pg-mem is an experimental in-memory emulation of a postgres database.
❤ It works both in Node or in the browser.
⭐ this repo if you like this package, it helps to motivate me :)
👉 See it in action with pg-mem playground
📐 Usage
Using Node.js
As always, it starts with an:
npm i pg-mem --save
Then, assuming you're using something like webpack, if you're targeting a browser:
import { newDb } from "pg-mem";
const db = newDb();
db.public.many();
Using Deno
Pretty straightforward :)
import { newDb } from "https://deno.land/x/pg_mem/mod.ts";
const db = newDb();
db.public.many();
Only use the SQL syntax parser
❤ Head to the pgsql-ast-parser repo
⚠ Disclaimer
The sql syntax parser is home-made. Which means that some features are not implemented, and will be considered as invalid syntaxes.
This lib is quite new, so forgive it if some obvious pg syntax is not supported !
... And open an issue if you feel like a feature should be implemented :)
Moreover, even if I wrote hundreds of tests, keep in mind that this implementation is a best effort to replicate PG.
Keep an eye on your query results if you perform complex queries.
Please file issues if some results seem incoherent with what should be returned.
Finally, I invite you to read the below section to have an idea of you can or cannot do.
🔍 Features
Rollback to a previous state
pg-mem
uses immutable data structures (here and here),
which means that you can have restore points for free!
This is super useful if you intend to use pg-mem
to mock your database for unit tests.
You could:
- Create your schema only once (which could be a heavy operation for a single unit test)
- Insert test data which will be shared by all test
- Create a restore point
- Run your tests with the same db instance, executing a
backup.restore()
before each test (which instantly resets db to the state it has after creating the restore point)
Usage:
const db = newDb();
db.public.none(`create table test(id text);
insert into test values ('value');`);
const backup = db.backup();
db.public.none(`update test set id='new value';`);
backup.restore();
db.public.many(`select * from test`);
Custom functions
You can declare custom functions like this:
db.public.registerFunction({
name: "say_hello",
args: [DataType.text],
returns: DataType.text,
implementation: (x) => "hello " + x,
});
And then use them like in SQL select say_hello('world')
.
Custom functions support overloading and variadic arguments.
⚠ However, the value you return is not type checked. It MUST correspond to the datatype you provided as 'returns' (it won't fail if not, but could lead to weird bugs).
Custom types
Not all pg types are implemented in pg-mem.
That said, most of the types are often equivalent to other types, with a format validation. pg-mem provides a way to register such types.
For instance, lets say you'd like to register the MACADDR type, which is basically a string, with a format constraint.
You can register it like this:
db.public.registerEquivalentType({
name: "macaddr",
equivalentTo: DataType.text,
isValid(val: string) {
return isValidMacAddress(val);
},
});
Doing so, you'll be able to do things such as:
SELECT '08:00:2b:01:02:03:04:05'::macaddr;
SELECT 'invalid'::macaddr;
If you feel your implementation of a type matches the standard, and would like to include it in pg-mem for others to enjoy it, please consider filing a pull request ! (tip: see the INET type implementation as an example, and the pg_catalog index where supported types are registered)
Extensions
No native extension is implemented (pull requests are welcome), but you can define kind-of extensions like this:
db.registerExtension("my-ext", (schema) => {
});
Statements like create extension "my-ext"
will then be supported.
📃 Libraries adapters
pg-mem provides handy shortcuts to create instances of popular libraries that will be bound to pg-mem instead of a real postgres db.
- pg-native
- node-postgres (pg)
- pg-promise (pgp)
- slonik
- typeorm
- knex
- mikro-orm
See the wiki for more details
💥 Inspection
Intercept queries
If you would like to hook your database, and return ad-hoc results, you can do so like this:
const db = newDb();
db.public.interceptQueries((sql) => {
if (sql === "select * from whatever") {
return [{ something: 42 }];
}
return null;
});
Inspect a table
You can manually inspect a table content using the find()
method:
for (const item of db.public.getTable<TItem>("mytable").find(itemTemplate)) {
console.log(item);
}
Manually insert items
If you'd like to insert items manually into a table, you can do this like that:
db.public.getTable<TItem>('mytable').insert({ }))
Subscribe to events
You can subscribe to some events, like:
const db = newDb();
db.on("query", (sql) => {});
db.on("query-failed", (sql) => {});
db.on("schema-change", () => {});
db.on("create-extension", (ext) => {});
Experimental events
pg-mem
implements a basic support for indices.
These handlers are called when a request cannot be optimized using one of the created indices.
However, a real postgres instance will be much smarter to optimize its requests... so when pg-mem
says "this request does not use an index", dont take my word for it.
db.on('seq-scan', () => {});
db.getTable('myTable').on('seq-scan', () = {});
db.on('catastrophic-join-optimization', () => {});
🙋♂️ FAQ
Detailed answers in the wiki
⚠️ Current limitations
- Materialized views are implemented as views (meaning that they are always up-to-date, without needing them to refresh)
- Indices implementations are basic
- No support for timezones
- All number-like types are all handled as javascript numbers, meaning that types like
numeric(x,y)
could not behave as expected.
🐜 Development
Pull requests are welcome :)
To start hacking this lib, you'll have to:
... once done, tests should appear. HMR is on, which means that changes in your code are instantly propagated to unit tests.
This allows for ultra fast development cycles (running tests takes less than 1 sec).
To debug tests: Just hit "run" (F5, or whatever)... VS Code should attach the mocha worker. Then run the test you want to debug.
Alternatively, you could just run npm run test
without installing anything, but this is a bit long.