Skip to content

Commit 8699d6f

Browse files
committed
Merge remote-tracking branch 'jsonb/release'
- Bringing in changes from other ShareDB-Postgres fork
2 parents 62bc7da + 19210ee commit 8699d6f

File tree

7 files changed

+1507
-110
lines changed

7 files changed

+1507
-110
lines changed

.eslintrc.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"env": {
3+
"es6": true,
4+
"node": true
5+
},
6+
"extends": "eslint:recommended",
7+
"rules": {
8+
"no-console": "off",
9+
"indent": [
10+
"error",
11+
2,
12+
{
13+
"FunctionDeclaration": {
14+
"parameters": "first"
15+
},
16+
"FunctionExpression": {
17+
"parameters": "first"
18+
},
19+
"CallExpression": {
20+
"arguments": 1
21+
},
22+
"SwitchCase": 1
23+
}
24+
],
25+
"quotes": [
26+
"error",
27+
"double",
28+
{
29+
"allowTemplateLiterals": true
30+
}
31+
],
32+
"semi": [
33+
"error",
34+
"always"
35+
]
36+
},
37+
"parserOptions": {
38+
"ecmaVersion": 2017
39+
}
40+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
3+
.idea

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,47 @@ Doesn't support queries (yet?).
88
Moderately experimental. (This drives [Synaptograph](https://www.synaptograph.com)'s backend, and [@nornagon](https://github.com/nornagon) hasn't noticed any issues so far.)
99

1010

11+
## Requirements
12+
13+
Due to the fix to resolve [high concurency issues](https://github.com/share/sharedb-postgres/issues/1) Postgres 9.5+ is now required.
14+
15+
## Migrating older versions
16+
17+
Older versions of this adaptor used the data type json. You will need to alter the data type prior to using if you are upgrading.
18+
19+
```PLpgSQL
20+
ALTER TABLE ops
21+
ALTER COLUMN operation
22+
SET DATA TYPE jsonb
23+
USING operation::jsonb;
24+
25+
ALTER TABLE snapshots
26+
ALTER COLUMN data
27+
SET DATA TYPE jsonb
28+
USING data::jsonb;
29+
```
30+
1131
## Usage
1232

1333
`sharedb-postgres` wraps native [node-postgres](https://github.com/brianc/node-postgres), and it supports the same configuration options.
1434

1535
To instantiate a sharedb-postgres wrapper, invoke the module and pass in your
16-
PostgreSQL configuration as an argument. For example:
36+
PostgreSQL configuration as an argument or use environmental arguments.
37+
38+
For example using environmental arugments:
39+
40+
```js
41+
var db = require('sharedb-postgres')();
42+
var backend = require('sharedb')({db: db})
43+
```
44+
45+
Then executing via the command line
46+
47+
```
48+
PGUSER=dbuser PGPASSWORD=secretpassword PGHOST=database.server.com PGDATABASE=mydb PGPORT=5433 npm start
49+
```
50+
51+
Example using an object
1752

1853
```js
1954
var db = require('sharedb-postgres')({host: 'localhost', database: 'mydb'});

index.js

Lines changed: 89 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
var DB = require('sharedb').DB;
2-
var pg = require('pg');
1+
var DB = require("sharedb").DB;
2+
var pg = require("pg");
33

44
// Postgres-backed ShareDB database
55

@@ -15,22 +15,17 @@ module.exports = PostgresDB;
1515

1616
PostgresDB.prototype = Object.create(DB.prototype);
1717

18-
PostgresDB.prototype.close = function(callback) {
18+
PostgresDB.prototype.close = function (callback) {
1919
this.closed = true;
2020
this.pool.end();
21-
21+
2222
if (callback) callback();
2323
};
2424

25-
function rollback(client, done) {
26-
client.query('ROLLBACK', function(err) {
27-
return done(err);
28-
})
29-
}
3025

3126
// Persists an op and snapshot if it is for the next version. Calls back with
3227
// callback(err, succeeded)
33-
PostgresDB.prototype.commit = function(collection, id, op, snapshot, options, callback) {
28+
PostgresDB.prototype.commit = function (collection, id, op, snapshot, options, callback) {
3429
/*
3530
* op: CreateOp {
3631
* src: '24545654654646',
@@ -41,121 +36,109 @@ PostgresDB.prototype.commit = function(collection, id, op, snapshot, options, ca
4136
* }
4237
* snapshot: PostgresSnapshot
4338
*/
44-
this.pool.connect(function(err, client, done) {
39+
this.pool.connect((err, client, done) => {
4540
if (err) {
4641
done(client);
4742
callback(err);
4843
return;
4944
}
50-
function commit() {
51-
client.query('COMMIT', function(err) {
52-
done(err);
53-
if (err) {
54-
callback(err);
55-
} else {
56-
callback(null, true);
57-
}
58-
})
59-
}
60-
// ZW: Op versions start from 0, snapshot versions start from 1. To get
61-
// the next snapshot version, we should query the snapshots table, not
62-
// the ops table.
63-
// (cf: the MemoryDB adapter, which uses the number of op rows, rather
64-
// than the highest op version)
65-
client.query(
66-
'SELECT max(version) AS max_version FROM snapshots WHERE collection = $1 AND doc_id = $2',
67-
[collection, id],
68-
function(err, res) {
69-
var max_version = res.rows[0].max_version;
70-
if (max_version == null)
71-
max_version = 0;
72-
if (snapshot.v !== max_version + 1) {
73-
return callback(null, false);
74-
}
75-
client.query('BEGIN', function(err) {
76-
// ZW: Snapshot version is always 1 above op version. We should
77-
// use op.v here, not snapshot.v
78-
client.query(
79-
'INSERT INTO ops (collection, doc_id, version, operation) VALUES ($1, $2, $3, $4)',
80-
[collection, id, op.v, op],
81-
function(err, res) {
82-
if (err) {
83-
// TODO: if err is "constraint violation", callback(null, false) instead
84-
rollback(client, done);
85-
callback(err);
86-
return;
87-
}
88-
if (snapshot.v === 1) {
89-
client.query(
90-
'INSERT INTO snapshots (collection, doc_id, doc_type, version, data) VALUES ($1, $2, $3, $4, $5)',
91-
[collection, id, snapshot.type, snapshot.v, snapshot.data],
92-
function(err, res) {
93-
// TODO:
94-
// if the insert was successful and did insert, callback(null, true)
95-
// if the insert was successful and did not insert, callback(null, false)
96-
// if there was an error, rollback and callback(error)
97-
if (err) {
98-
rollback(client, done);
99-
callback(err);
100-
return;
101-
}
102-
commit();
103-
}
104-
)
105-
} else {
106-
client.query(
107-
'UPDATE snapshots SET doc_type = $3, version = $4, data = $5 WHERE collection = $1 AND doc_id = $2 AND version = ($4 - 1)',
108-
[collection, id, snapshot.type, snapshot.v, snapshot.data],
109-
function(err, res) {
110-
// TODO:
111-
// if any rows were updated, success
112-
// if 0 rows were updated, rollback and not success
113-
// if error, rollback and not success
114-
if (err) {
115-
rollback(client, done);
116-
callback(err);
117-
return;
118-
}
119-
commit();
120-
}
121-
)
122-
}
123-
}
124-
)
125-
})
45+
46+
/*
47+
* This query uses common table expression to upsert the snapshot table
48+
* (iff the new version is exactly 1 more than the latest table or if
49+
* the document id does not exists)
50+
*
51+
* It will then insert into the ops table if it is exactly 1 more than the
52+
* latest table or it the first operation and iff the previous insert into
53+
* the snapshot table is successful.
54+
*
55+
* This result of this query the version of the newly inserted operation
56+
* If either the ops or the snapshot insert fails then 0 rows are returned
57+
*
58+
* If 0 zeros are return then the callback must return false
59+
*
60+
* Casting is required as postgres thinks that collection and doc_id are
61+
* not varchar
62+
*/
63+
const query = {
64+
name: "sdb-commit-op-and-snap",
65+
text: `WITH snapshot_id AS (
66+
INSERT INTO snapshots (collection, doc_id, doc_type, version, data)
67+
SELECT $1::varchar collection, $2::varchar doc_id, $4 doc_type, $3 v, $5 d
68+
WHERE $3 = (
69+
SELECT version+1 v
70+
FROM snapshots
71+
WHERE collection = $1 AND doc_id = $2
72+
FOR UPDATE
73+
) OR NOT EXISTS (
74+
SELECT 1
75+
FROM snapshots
76+
WHERE collection = $1 AND doc_id = $2
77+
FOR UPDATE
78+
)
79+
ON CONFLICT (collection, doc_id) DO UPDATE SET version = $3, data = $5, doc_type = $4
80+
RETURNING version
81+
)
82+
INSERT INTO ops (collection, doc_id, version, operation)
83+
SELECT $1::varchar collection, $2::varchar doc_id, $3 v, $6 operation
84+
WHERE (
85+
$3 = (
86+
SELECT max(version)+1
87+
FROM ops
88+
WHERE collection = $1 AND doc_id = $2
89+
) OR NOT EXISTS (
90+
SELECT 1
91+
FROM ops
92+
WHERE collection = $1 AND doc_id = $2
93+
)
94+
) AND EXISTS (SELECT 1 FROM snapshot_id)
95+
RETURNING version`,
96+
values: [collection, id, snapshot.v, snapshot.type, snapshot.data, op]
97+
};
98+
client.query(query, (err, res) => {
99+
if (err) {
100+
callback(err);
101+
} else if (res.rows.length === 0) {
102+
done(client);
103+
callback(null, false);
104+
}
105+
else {
106+
done(client);
107+
callback(null, true);
126108
}
127-
)
128-
})
109+
});
110+
111+
});
129112
};
130113

131114
// Get the named document from the database. The callback is called with (err,
132115
// snapshot). A snapshot with a version of zero is returned if the docuemnt
133116
// has never been created in the database.
134-
PostgresDB.prototype.getSnapshot = function(collection, id, fields, options, callback) {
135-
this.pool.connect(function(err, client, done) {
117+
PostgresDB.prototype.getSnapshot = function (collection, id, fields, options, callback) {
118+
this.pool.connect(function (err, client, done) {
136119
if (err) {
137120
done(client);
138121
callback(err);
139122
return;
140123
}
141124
client.query(
142-
'SELECT version, data, doc_type FROM snapshots WHERE collection = $1 AND doc_id = $2 LIMIT 1',
125+
"SELECT version, data, doc_type FROM snapshots WHERE collection = $1 AND doc_id = $2 LIMIT 1",
143126
[collection, id],
144-
function(err, res) {
127+
function (err, res) {
145128
done();
146129
if (err) {
147130
callback(err);
148131
return;
149132
}
150133
if (res.rows.length) {
151-
var row = res.rows[0]
134+
var row = res.rows[0];
152135
var snapshot = new PostgresSnapshot(
153136
id,
154137
row.version,
155138
row.doc_type,
156139
row.data,
157140
undefined // TODO: metadata
158-
)
141+
);
159142
callback(null, snapshot);
160143
} else {
161144
var snapshot = new PostgresSnapshot(
@@ -164,12 +147,12 @@ PostgresDB.prototype.getSnapshot = function(collection, id, fields, options, cal
164147
null,
165148
undefined,
166149
undefined
167-
)
150+
);
168151
callback(null, snapshot);
169152
}
170153
}
171-
)
172-
})
154+
);
155+
});
173156
};
174157

175158
// Get operations between [from, to) noninclusively. (Ie, the range should
@@ -181,8 +164,8 @@ PostgresDB.prototype.getSnapshot = function(collection, id, fields, options, cal
181164
// The version will be inferred from the parameters if it is missing.
182165
//
183166
// Callback should be called as callback(error, [list of ops]);
184-
PostgresDB.prototype.getOps = function(collection, id, from, to, options, callback) {
185-
this.pool.connect(function(err, client, done) {
167+
PostgresDB.prototype.getOps = function (collection, id, from, to, options, callback) {
168+
this.pool.connect(function (err, client, done) {
186169
if (err) {
187170
done(client);
188171
callback(err);
@@ -191,21 +174,21 @@ PostgresDB.prototype.getOps = function(collection, id, from, to, options, callba
191174

192175
// ZW: Add explicit row ordering here
193176
client.query(
194-
'SELECT version, operation FROM ops WHERE collection = $1 AND doc_id =' +
195-
' $2 AND version >= $3 AND version < $4 ORDER BY version ASC',
177+
"SELECT version, operation FROM ops WHERE collection = $1 AND doc_id =" +
178+
" $2 AND version >= $3 AND version < $4 ORDER BY version ASC",
196179
[collection, id, from, to],
197-
function(err, res) {
180+
function (err, res) {
198181
done();
199182
if (err) {
200183
callback(err);
201184
return;
202185
}
203-
callback(null, res.rows.map(function(row) {
186+
callback(null, res.rows.map(function (row) {
204187
return row.operation;
205188
}));
206189
}
207-
)
208-
})
190+
);
191+
});
209192
};
210193

211194
function PostgresSnapshot(id, version, type, data, meta) {

0 commit comments

Comments
 (0)