mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	PostgreSQL database driver (#5861)
* [test/database/list] Fix test list 4 being used in two different tests * [database/postgres] PostgreSQL database driver * [database/postgres] Make transactions work based on continuation scope. * [database/postgres] Implement nested transactions * eslint --fix * Add database changes from earlier this week to the PostgreSQL driver. * Fix typo * Fix postgres.incrObjectFieldBy returning undefined instead of null when given NaN * [database/postgres] Fix sortedSetsCard returning an array of strings. * Update socket.io postgres adapter * Fix PostgreSQL erroring when multiple updates are made to the same sorted set entry in a single operation. Add a test case to catch this error. * Fix lint errors. * Only prune sessions on one instance in a cluster to avoid deadlocks. They're caught and handled by the database server, but they spam the logs. * Fix arguments.slice.
This commit is contained in:
		
							
								
								
									
										239
									
								
								src/database/postgres/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								src/database/postgres/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
|  | ||||
| module.exports = function (db, module) { | ||||
| 	var helpers = module.helpers.postgres; | ||||
|  | ||||
| 	var query = db.query.bind(db); | ||||
|  | ||||
| 	module.flushdb = function (callback) { | ||||
| 		callback = callback || helpers.noop; | ||||
|  | ||||
| 		async.series([ | ||||
| 			async.apply(query, `DROP SCHEMA "public" CASCADE`), | ||||
| 			async.apply(query, `CREATE SCHEMA "public"`), | ||||
| 		], function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	module.emptydb = function (callback) { | ||||
| 		callback = callback || helpers.noop; | ||||
| 		query(`DELETE FROM "legacy_object"`, function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	module.exists = function (key, callback) { | ||||
| 		if (!key) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		query({ | ||||
| 			name: 'exists', | ||||
| 			text: ` | ||||
| SELECT EXISTS(SELECT * | ||||
|                 FROM "legacy_object_live" | ||||
|                WHERE "_key" = $1::TEXT | ||||
|                LIMIT 1) e`, | ||||
| 			values: [key], | ||||
| 		}, function (err, res) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			callback(null, res.rows[0].e); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	module.delete = function (key, callback) { | ||||
| 		callback = callback || helpers.noop; | ||||
| 		if (!key) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		query({ | ||||
| 			name: 'delete', | ||||
| 			text: ` | ||||
| DELETE FROM "legacy_object" | ||||
|  WHERE "_key" = $1::TEXT`, | ||||
| 			values: [key], | ||||
| 		}, function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	module.deleteAll = function (keys, callback) { | ||||
| 		callback = callback || helpers.noop; | ||||
|  | ||||
| 		if (!Array.isArray(keys) || !keys.length) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		query({ | ||||
| 			name: 'deleteAll', | ||||
| 			text: ` | ||||
| DELETE FROM "legacy_object" | ||||
|  WHERE "_key" = ANY($1::TEXT[])`, | ||||
| 			values: [keys], | ||||
| 		}, function (err) { | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	module.get = function (key, callback) { | ||||
| 		if (!key) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		query({ | ||||
| 			name: 'get', | ||||
| 			text: ` | ||||
| SELECT s."data" t | ||||
|   FROM "legacy_object_live" o | ||||
|  INNER JOIN "legacy_string" s | ||||
|          ON o."_key" = s."_key" | ||||
|         AND o."type" = s."type" | ||||
|  WHERE o."_key" = $1::TEXT | ||||
|  LIMIT 1`, | ||||
| 			values: [key], | ||||
| 		}, function (err, res) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			if (res.rows.length) { | ||||
| 				return callback(null, res.rows[0].t); | ||||
| 			} | ||||
|  | ||||
| 			callback(null, null); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	module.set = function (key, value, callback) { | ||||
| 		callback = callback || helpers.noop; | ||||
|  | ||||
| 		if (!key) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		module.transaction(function (tx, done) { | ||||
| 			async.series([ | ||||
| 				async.apply(helpers.ensureLegacyObjectType, tx.client, key, 'string'), | ||||
| 				async.apply(tx.client.query.bind(tx.client), { | ||||
| 					name: 'set', | ||||
| 					text: ` | ||||
| INSERT INTO "legacy_string" ("_key", "data") | ||||
| VALUES ($1::TEXT, $2::TEXT) | ||||
|     ON CONFLICT ("_key") | ||||
|     DO UPDATE SET "data" = $2::TEXT`, | ||||
| 					values: [key, value], | ||||
| 				}), | ||||
| 			], function (err) { | ||||
| 				done(err); | ||||
| 			}); | ||||
| 		}, callback); | ||||
| 	}; | ||||
|  | ||||
| 	module.increment = function (key, callback) { | ||||
| 		callback = callback || helpers.noop; | ||||
|  | ||||
| 		if (!key) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		module.transaction(function (tx, done) { | ||||
| 			async.waterfall([ | ||||
| 				async.apply(helpers.ensureLegacyObjectType, tx.client, key, 'string'), | ||||
| 				async.apply(tx.client.query.bind(tx.client), { | ||||
| 					name: 'increment', | ||||
| 					text: ` | ||||
| INSERT INTO "legacy_string" ("_key", "data") | ||||
| VALUES ($1::TEXT, '1') | ||||
|     ON CONFLICT ("_key") | ||||
|     DO UPDATE SET "data" = ("legacy_string"."data"::NUMERIC + 1)::TEXT | ||||
| RETURNING "data" d`, | ||||
| 					values: [key], | ||||
| 				}), | ||||
| 			], function (err, res) { | ||||
| 				if (err) { | ||||
| 					return done(err); | ||||
| 				} | ||||
|  | ||||
| 				done(null, parseFloat(res.rows[0].d)); | ||||
| 			}); | ||||
| 		}, callback); | ||||
| 	}; | ||||
|  | ||||
| 	module.rename = function (oldKey, newKey, callback) { | ||||
| 		module.transaction(function (tx, done) { | ||||
| 			async.series([ | ||||
| 				async.apply(tx.delete, newKey), | ||||
| 				async.apply(tx.client.query.bind(tx.client), { | ||||
| 					name: 'rename', | ||||
| 					text: ` | ||||
| UPDATE "legacy_object" | ||||
|    SET "_key" = $2::TEXT | ||||
|  WHERE "_key" = $1::TEXT`, | ||||
| 					values: [oldKey, newKey], | ||||
| 				}), | ||||
| 			], function (err) { | ||||
| 				done(err); | ||||
| 			}); | ||||
| 		}, callback || helpers.noop); | ||||
| 	}; | ||||
|  | ||||
| 	module.type = function (key, callback) { | ||||
| 		query({ | ||||
| 			name: 'type', | ||||
| 			text: ` | ||||
| SELECT "type"::TEXT t | ||||
|   FROM "legacy_object_live" | ||||
|  WHERE "_key" = $1::TEXT | ||||
|  LIMIT 1`, | ||||
| 			values: [key], | ||||
| 		}, function (err, res) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			if (res.rows.length) { | ||||
| 				return callback(null, res.rows[0].t); | ||||
| 			} | ||||
|  | ||||
| 			callback(null, null); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	function doExpire(key, date, callback) { | ||||
| 		query({ | ||||
| 			name: 'expire', | ||||
| 			text: ` | ||||
| UPDATE "legacy_object" | ||||
|    SET "expireAt" = $2::TIMESTAMPTZ | ||||
|  WHERE "_key" = $1::TEXT`, | ||||
| 			values: [key, date], | ||||
| 		}, function (err) { | ||||
| 			if (callback) { | ||||
| 				callback(err); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	module.expire = function (key, seconds, callback) { | ||||
| 		doExpire(key, new Date(((Date.now() / 1000) + seconds) * 1000), callback); | ||||
| 	}; | ||||
|  | ||||
| 	module.expireAt = function (key, timestamp, callback) { | ||||
| 		doExpire(key, new Date(timestamp * 1000), callback); | ||||
| 	}; | ||||
|  | ||||
| 	module.pexpire = function (key, ms, callback) { | ||||
| 		doExpire(key, new Date(Date.now() + parseInt(ms, 10)), callback); | ||||
| 	}; | ||||
|  | ||||
| 	module.pexpireAt = function (key, timestamp, callback) { | ||||
| 		doExpire(key, new Date(timestamp), callback); | ||||
| 	}; | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user