neopg 0.0.0
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/index.js +6 -0
- package/lib/ModelChain.js +364 -0
- package/lib/ModelDef.js +204 -0
- package/lib/NeoPG.js +82 -0
- package/lib/SchemaSync.js +506 -0
- package/lib/TransactionScope.js +29 -0
- package/lib/dataTypes.js +60 -0
- package/lib/forbidColumns.js +29 -0
- package/lib/makeId.js +202 -0
- package/lib/makeTimestamp.js +28 -0
- package/lib/randstring.js +23 -0
- package/package.json +28 -0
- package/postgres/bytes.js +78 -0
- package/postgres/connection.js +1042 -0
- package/postgres/errors.js +53 -0
- package/postgres/index.js +566 -0
- package/postgres/large.js +70 -0
- package/postgres/query.js +173 -0
- package/postgres/queue.js +31 -0
- package/postgres/result.js +16 -0
- package/postgres/subscribe.js +277 -0
- package/postgres/types.js +367 -0
- package/test/test-db.js +44 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const originCache = new Map()
|
|
2
|
+
, originStackCache = new Map()
|
|
3
|
+
, originError = Symbol('OriginError')
|
|
4
|
+
|
|
5
|
+
const CLOSE = module.exports.CLOSE = {}
|
|
6
|
+
const Query = module.exports.Query = class Query extends Promise {
|
|
7
|
+
constructor(strings, args, handler, canceller, options = {}) {
|
|
8
|
+
let resolve
|
|
9
|
+
, reject
|
|
10
|
+
|
|
11
|
+
super((a, b) => {
|
|
12
|
+
resolve = a
|
|
13
|
+
reject = b
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
this.tagged = Array.isArray(strings.raw)
|
|
17
|
+
this.strings = strings
|
|
18
|
+
this.args = args
|
|
19
|
+
this.handler = handler
|
|
20
|
+
this.canceller = canceller
|
|
21
|
+
this.options = options
|
|
22
|
+
|
|
23
|
+
this.state = null
|
|
24
|
+
this.statement = null
|
|
25
|
+
|
|
26
|
+
this.resolve = x => (this.active = false, resolve(x))
|
|
27
|
+
this.reject = x => (this.active = false, reject(x))
|
|
28
|
+
|
|
29
|
+
this.active = false
|
|
30
|
+
this.cancelled = null
|
|
31
|
+
this.executed = false
|
|
32
|
+
this.signature = ''
|
|
33
|
+
|
|
34
|
+
this[originError] = this.handler.debug
|
|
35
|
+
? new Error()
|
|
36
|
+
: this.tagged && cachedError(this.strings)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get origin() {
|
|
40
|
+
return (this.handler.debug
|
|
41
|
+
? this[originError].stack
|
|
42
|
+
: this.tagged && originStackCache.has(this.strings)
|
|
43
|
+
? originStackCache.get(this.strings)
|
|
44
|
+
: originStackCache.set(this.strings, this[originError].stack).get(this.strings)
|
|
45
|
+
) || ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static get [Symbol.species]() {
|
|
49
|
+
return Promise
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cancel() {
|
|
53
|
+
return this.canceller && (this.canceller(this), this.canceller = null)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
simple() {
|
|
57
|
+
this.options.simple = true
|
|
58
|
+
this.options.prepare = false
|
|
59
|
+
return this
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async readable() {
|
|
63
|
+
this.simple()
|
|
64
|
+
this.streaming = true
|
|
65
|
+
return this
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async writable() {
|
|
69
|
+
this.simple()
|
|
70
|
+
this.streaming = true
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
cursor(rows = 1, fn) {
|
|
75
|
+
this.options.simple = false
|
|
76
|
+
if (typeof rows === 'function') {
|
|
77
|
+
fn = rows
|
|
78
|
+
rows = 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.cursorRows = rows
|
|
82
|
+
|
|
83
|
+
if (typeof fn === 'function')
|
|
84
|
+
return (this.cursorFn = fn, this)
|
|
85
|
+
|
|
86
|
+
let prev
|
|
87
|
+
return {
|
|
88
|
+
[Symbol.asyncIterator]: () => ({
|
|
89
|
+
next: () => {
|
|
90
|
+
if (this.executed && !this.active)
|
|
91
|
+
return { done: true }
|
|
92
|
+
|
|
93
|
+
prev && prev()
|
|
94
|
+
const promise = new Promise((resolve, reject) => {
|
|
95
|
+
this.cursorFn = value => {
|
|
96
|
+
resolve({ value, done: false })
|
|
97
|
+
return new Promise(r => prev = r)
|
|
98
|
+
}
|
|
99
|
+
this.resolve = () => (this.active = false, resolve({ done: true }))
|
|
100
|
+
this.reject = x => (this.active = false, reject(x))
|
|
101
|
+
})
|
|
102
|
+
this.execute()
|
|
103
|
+
return promise
|
|
104
|
+
},
|
|
105
|
+
return() {
|
|
106
|
+
prev && prev(CLOSE)
|
|
107
|
+
return { done: true }
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe() {
|
|
114
|
+
this.options.simple = false
|
|
115
|
+
this.onlyDescribe = this.options.prepare = true
|
|
116
|
+
return this
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
stream() {
|
|
120
|
+
throw new Error('.stream has been renamed to .forEach')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
forEach(fn) {
|
|
124
|
+
this.forEachFn = fn
|
|
125
|
+
this.handle()
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
raw() {
|
|
130
|
+
this.isRaw = true
|
|
131
|
+
return this
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
values() {
|
|
135
|
+
this.isRaw = 'values'
|
|
136
|
+
return this
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async handle() {
|
|
140
|
+
!this.executed && (this.executed = true) && await 1 && this.handler(this)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
execute() {
|
|
144
|
+
this.handle()
|
|
145
|
+
return this
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
then() {
|
|
149
|
+
this.handle()
|
|
150
|
+
return super.then.apply(this, arguments)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
catch() {
|
|
154
|
+
this.handle()
|
|
155
|
+
return super.catch.apply(this, arguments)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
finally() {
|
|
159
|
+
this.handle()
|
|
160
|
+
return super.finally.apply(this, arguments)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function cachedError(xs) {
|
|
165
|
+
if (originCache.has(xs))
|
|
166
|
+
return originCache.get(xs)
|
|
167
|
+
|
|
168
|
+
const x = Error.stackTraceLimit
|
|
169
|
+
Error.stackTraceLimit = 4
|
|
170
|
+
originCache.set(xs, new Error())
|
|
171
|
+
Error.stackTraceLimit = x
|
|
172
|
+
return originCache.get(xs)
|
|
173
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module.exports = Queue
|
|
2
|
+
|
|
3
|
+
function Queue(initial = []) {
|
|
4
|
+
let xs = initial.slice()
|
|
5
|
+
let index = 0
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
get length() {
|
|
9
|
+
return xs.length - index
|
|
10
|
+
},
|
|
11
|
+
remove: (x) => {
|
|
12
|
+
const index = xs.indexOf(x)
|
|
13
|
+
return index === -1
|
|
14
|
+
? null
|
|
15
|
+
: (xs.splice(index, 1), x)
|
|
16
|
+
},
|
|
17
|
+
push: (x) => (xs.push(x), x),
|
|
18
|
+
shift: () => {
|
|
19
|
+
const out = xs[index++]
|
|
20
|
+
|
|
21
|
+
if (index === xs.length) {
|
|
22
|
+
index = 0
|
|
23
|
+
xs = []
|
|
24
|
+
} else {
|
|
25
|
+
xs[index - 1] = undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return out
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = class Result extends Array {
|
|
2
|
+
constructor() {
|
|
3
|
+
super()
|
|
4
|
+
Object.defineProperties(this, {
|
|
5
|
+
count: { value: null, writable: true },
|
|
6
|
+
state: { value: null, writable: true },
|
|
7
|
+
command: { value: null, writable: true },
|
|
8
|
+
columns: { value: null, writable: true },
|
|
9
|
+
statement: { value: null, writable: true }
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static get [Symbol.species]() {
|
|
14
|
+
return Array
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
const noop = () => { /* noop */ }
|
|
2
|
+
|
|
3
|
+
module.exports = Subscribe;function Subscribe(postgres, options) {
|
|
4
|
+
const subscribers = new Map()
|
|
5
|
+
, slot = 'postgresjs_' + Math.random().toString(36).slice(2)
|
|
6
|
+
, state = {}
|
|
7
|
+
|
|
8
|
+
let connection
|
|
9
|
+
, stream
|
|
10
|
+
, ended = false
|
|
11
|
+
|
|
12
|
+
const sql = subscribe.sql = postgres({
|
|
13
|
+
...options,
|
|
14
|
+
transform: { column: {}, value: {}, row: {} },
|
|
15
|
+
max: 1,
|
|
16
|
+
fetch_types: false,
|
|
17
|
+
idle_timeout: null,
|
|
18
|
+
max_lifetime: null,
|
|
19
|
+
connection: {
|
|
20
|
+
...options.connection,
|
|
21
|
+
replication: 'database'
|
|
22
|
+
},
|
|
23
|
+
onclose: async function() {
|
|
24
|
+
if (ended)
|
|
25
|
+
return
|
|
26
|
+
stream = null
|
|
27
|
+
state.pid = state.secret = undefined
|
|
28
|
+
connected(await init(sql, slot, options.publications))
|
|
29
|
+
subscribers.forEach(event => event.forEach(({ onsubscribe }) => onsubscribe()))
|
|
30
|
+
},
|
|
31
|
+
no_subscribe: true
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const end = sql.end
|
|
35
|
+
, close = sql.close
|
|
36
|
+
|
|
37
|
+
sql.end = async() => {
|
|
38
|
+
ended = true
|
|
39
|
+
stream && (await new Promise(r => (stream.once('close', r), stream.end())))
|
|
40
|
+
return end()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
sql.close = async() => {
|
|
44
|
+
stream && (await new Promise(r => (stream.once('close', r), stream.end())))
|
|
45
|
+
return close()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return subscribe
|
|
49
|
+
|
|
50
|
+
async function subscribe(event, fn, onsubscribe = noop, onerror = noop) {
|
|
51
|
+
event = parseEvent(event)
|
|
52
|
+
|
|
53
|
+
if (!connection)
|
|
54
|
+
connection = init(sql, slot, options.publications)
|
|
55
|
+
|
|
56
|
+
const subscriber = { fn, onsubscribe }
|
|
57
|
+
const fns = subscribers.has(event)
|
|
58
|
+
? subscribers.get(event).add(subscriber)
|
|
59
|
+
: subscribers.set(event, new Set([subscriber])).get(event)
|
|
60
|
+
|
|
61
|
+
const unsubscribe = () => {
|
|
62
|
+
fns.delete(subscriber)
|
|
63
|
+
fns.size === 0 && subscribers.delete(event)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return connection.then(x => {
|
|
67
|
+
connected(x)
|
|
68
|
+
onsubscribe()
|
|
69
|
+
stream && stream.on('error', onerror)
|
|
70
|
+
return { unsubscribe, state, sql }
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function connected(x) {
|
|
75
|
+
stream = x.stream
|
|
76
|
+
state.pid = x.state.pid
|
|
77
|
+
state.secret = x.state.secret
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function init(sql, slot, publications) {
|
|
81
|
+
if (!publications)
|
|
82
|
+
throw new Error('Missing publication names')
|
|
83
|
+
|
|
84
|
+
const xs = await sql.unsafe(
|
|
85
|
+
`CREATE_REPLICATION_SLOT ${ slot } TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const [x] = xs
|
|
89
|
+
|
|
90
|
+
const stream = await sql.unsafe(
|
|
91
|
+
`START_REPLICATION SLOT ${ slot } LOGICAL ${
|
|
92
|
+
x.consistent_point
|
|
93
|
+
} (proto_version '1', publication_names '${ publications }')`
|
|
94
|
+
).writable()
|
|
95
|
+
|
|
96
|
+
const state = {
|
|
97
|
+
lsn: Buffer.concat(x.consistent_point.split('/').map(x => Buffer.from(('00000000' + x).slice(-8), 'hex')))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
stream.on('data', data)
|
|
101
|
+
stream.on('error', error)
|
|
102
|
+
stream.on('close', sql.close)
|
|
103
|
+
|
|
104
|
+
return { stream, state: xs.state }
|
|
105
|
+
|
|
106
|
+
function error(e) {
|
|
107
|
+
console.error('Unexpected error during logical streaming - reconnecting', e) // eslint-disable-line
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function data(x) {
|
|
111
|
+
if (x[0] === 0x77) {
|
|
112
|
+
parse(x.subarray(25), state, sql.options.parsers, handle, options.transform)
|
|
113
|
+
} else if (x[0] === 0x6b && x[17]) {
|
|
114
|
+
state.lsn = x.subarray(1, 9)
|
|
115
|
+
pong()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handle(a, b) {
|
|
120
|
+
const path = b.relation.schema + '.' + b.relation.table
|
|
121
|
+
call('*', a, b)
|
|
122
|
+
call('*:' + path, a, b)
|
|
123
|
+
b.relation.keys.length && call('*:' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
|
|
124
|
+
call(b.command, a, b)
|
|
125
|
+
call(b.command + ':' + path, a, b)
|
|
126
|
+
b.relation.keys.length && call(b.command + ':' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function pong() {
|
|
130
|
+
const x = Buffer.alloc(34)
|
|
131
|
+
x[0] = 'r'.charCodeAt(0)
|
|
132
|
+
x.fill(state.lsn, 1)
|
|
133
|
+
x.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2000, 0, 1)) * BigInt(1000), 25)
|
|
134
|
+
stream.write(x)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function call(x, a, b) {
|
|
139
|
+
subscribers.has(x) && subscribers.get(x).forEach(({ fn }) => fn(a, b, x))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function Time(x) {
|
|
144
|
+
return new Date(Date.UTC(2000, 0, 1) + Number(x / BigInt(1000)))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parse(x, state, parsers, handle, transform) {
|
|
148
|
+
const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
|
|
149
|
+
|
|
150
|
+
Object.entries({
|
|
151
|
+
R: x => { // Relation
|
|
152
|
+
let i = 1
|
|
153
|
+
const r = state[x.readUInt32BE(i)] = {
|
|
154
|
+
schema: x.toString('utf8', i += 4, i = x.indexOf(0, i)) || 'pg_catalog',
|
|
155
|
+
table: x.toString('utf8', i + 1, i = x.indexOf(0, i + 1)),
|
|
156
|
+
columns: Array(x.readUInt16BE(i += 2)),
|
|
157
|
+
keys: []
|
|
158
|
+
}
|
|
159
|
+
i += 2
|
|
160
|
+
|
|
161
|
+
let columnIndex = 0
|
|
162
|
+
, column
|
|
163
|
+
|
|
164
|
+
while (i < x.length) {
|
|
165
|
+
column = r.columns[columnIndex++] = {
|
|
166
|
+
key: x[i++],
|
|
167
|
+
name: transform.column.from
|
|
168
|
+
? transform.column.from(x.toString('utf8', i, i = x.indexOf(0, i)))
|
|
169
|
+
: x.toString('utf8', i, i = x.indexOf(0, i)),
|
|
170
|
+
type: x.readUInt32BE(i += 1),
|
|
171
|
+
parser: parsers[x.readUInt32BE(i)],
|
|
172
|
+
atttypmod: x.readUInt32BE(i += 4)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
column.key && r.keys.push(column)
|
|
176
|
+
i += 4
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
Y: () => { /* noop */ }, // Type
|
|
180
|
+
O: () => { /* noop */ }, // Origin
|
|
181
|
+
B: x => { // Begin
|
|
182
|
+
state.date = Time(x.readBigInt64BE(9))
|
|
183
|
+
state.lsn = x.subarray(1, 9)
|
|
184
|
+
},
|
|
185
|
+
I: x => { // Insert
|
|
186
|
+
let i = 1
|
|
187
|
+
const relation = state[x.readUInt32BE(i)]
|
|
188
|
+
const { row } = tuples(x, relation.columns, i += 7, transform)
|
|
189
|
+
|
|
190
|
+
handle(row, {
|
|
191
|
+
command: 'insert',
|
|
192
|
+
relation
|
|
193
|
+
})
|
|
194
|
+
},
|
|
195
|
+
D: x => { // Delete
|
|
196
|
+
let i = 1
|
|
197
|
+
const relation = state[x.readUInt32BE(i)]
|
|
198
|
+
i += 4
|
|
199
|
+
const key = x[i] === 75
|
|
200
|
+
handle(key || x[i] === 79
|
|
201
|
+
? tuples(x, relation.columns, i += 3, transform).row
|
|
202
|
+
: null
|
|
203
|
+
, {
|
|
204
|
+
command: 'delete',
|
|
205
|
+
relation,
|
|
206
|
+
key
|
|
207
|
+
})
|
|
208
|
+
},
|
|
209
|
+
U: x => { // Update
|
|
210
|
+
let i = 1
|
|
211
|
+
const relation = state[x.readUInt32BE(i)]
|
|
212
|
+
i += 4
|
|
213
|
+
const key = x[i] === 75
|
|
214
|
+
const xs = key || x[i] === 79
|
|
215
|
+
? tuples(x, relation.columns, i += 3, transform)
|
|
216
|
+
: null
|
|
217
|
+
|
|
218
|
+
xs && (i = xs.i)
|
|
219
|
+
|
|
220
|
+
const { row } = tuples(x, relation.columns, i + 3, transform)
|
|
221
|
+
|
|
222
|
+
handle(row, {
|
|
223
|
+
command: 'update',
|
|
224
|
+
relation,
|
|
225
|
+
key,
|
|
226
|
+
old: xs && xs.row
|
|
227
|
+
})
|
|
228
|
+
},
|
|
229
|
+
T: () => { /* noop */ }, // Truncate,
|
|
230
|
+
C: () => { /* noop */ } // Commit
|
|
231
|
+
}).reduce(char, {})[x[0]](x)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function tuples(x, columns, xi, transform) {
|
|
235
|
+
let type
|
|
236
|
+
, column
|
|
237
|
+
, value
|
|
238
|
+
|
|
239
|
+
const row = transform.raw ? new Array(columns.length) : {}
|
|
240
|
+
for (let i = 0; i < columns.length; i++) {
|
|
241
|
+
type = x[xi++]
|
|
242
|
+
column = columns[i]
|
|
243
|
+
value = type === 110 // n
|
|
244
|
+
? null
|
|
245
|
+
: type === 117 // u
|
|
246
|
+
? undefined
|
|
247
|
+
: column.parser === undefined
|
|
248
|
+
? x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi))
|
|
249
|
+
: column.parser.array === true
|
|
250
|
+
? column.parser(x.toString('utf8', xi + 5, xi += 4 + x.readUInt32BE(xi)))
|
|
251
|
+
: column.parser(x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi)))
|
|
252
|
+
|
|
253
|
+
transform.raw
|
|
254
|
+
? (row[i] = transform.raw === true
|
|
255
|
+
? value
|
|
256
|
+
: transform.value.from ? transform.value.from(value, column) : value)
|
|
257
|
+
: (row[column.name] = transform.value.from
|
|
258
|
+
? transform.value.from(value, column)
|
|
259
|
+
: value
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { i: xi, row: transform.row.from ? transform.row.from(row) : row }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function parseEvent(x) {
|
|
267
|
+
const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || []
|
|
268
|
+
|
|
269
|
+
if (!xs)
|
|
270
|
+
throw new Error('Malformed subscribe pattern: ' + x)
|
|
271
|
+
|
|
272
|
+
const [, command, path, key] = xs
|
|
273
|
+
|
|
274
|
+
return (command || '*')
|
|
275
|
+
+ (path ? ':' + (path.indexOf('.') === -1 ? 'public.' + path : path) : '')
|
|
276
|
+
+ (key ? '=' + key : '')
|
|
277
|
+
}
|