Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 54 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/libsql-pg",
"version": "0.1.2",
"version": "0.1.3",
"type": "module",
"description": "Drop-in replacement for @libsql/client that talks to Postgres, plus the tools to move a Turso/libSQL database into it: the same execute/batch/transaction surface, SQLite idioms rewritten on the way through, a schema converter and a row copier.",
"keywords": [
Expand Down Expand Up @@ -47,7 +47,7 @@
"LICENSE"
],
"scripts": {
"test": "node --test \"test/*.test.js\"",
"test": "node --test test/*.test.js",
"test:unit": "node --test test/rewrite.test.js test/bind.test.js test/result.test.js test/errors.test.js test/schema.test.js test/copy-plan.test.js"
},
"engines": {
Expand Down
14 changes: 10 additions & 4 deletions src/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,12 @@ function convertColumnConstraints(constraints, { type, ctx, opts }) {
/** Remove a DEFAULT clause (a literal or a parenthesised expression). */
function stripDefault(c) {
const mask = codeMask(c);
const m = /(?<!\bby)\sdefault\s+/i.exec(mask);
const m = /(?<!\bby)\sdefault(?=\s)/i.exec(mask);
if (!m) return c;
// The mask blanks string literals to spaces, so the gap after DEFAULT is
// measured on the text: `default 'x' check (...)` must not skip past 'x'.
let end = m.index + m[0].length;
end += /^\s*/.exec(c.slice(end))[0].length;
if (mask[end] === '(') end = matchParen(mask, end) + 1;
else {
const rest = /^('[^']*(?:''[^']*)*'|[^\s]+)/.exec(c.slice(end));
Expand All @@ -175,9 +178,10 @@ function stripDefault(c) {
*/
function rewriteDefault(c, type, ctx, notes) {
const mask = codeMask(c);
const m = /(?<!\bby)\sdefault\s+/i.exec(mask);
const m = /(?<!\bby)\sdefault(?=\s)/i.exec(mask);
if (!m) return c;
const start = m.index + m[0].length;
let start = m.index + m[0].length;
start += /^\s*/.exec(c.slice(start))[0].length; // see stripDefault: literals are blank in the mask
let end;
if (mask[start] === '(') end = matchParen(mask, start) + 1;
else {
Expand All @@ -189,8 +193,10 @@ function rewriteDefault(c, type, ctx, notes) {
let out = bare;
const lower = bare.toLowerCase().replace(/\s+/g, '');
if (type === 'boolean' && /^[01]$/.test(bare)) out = bare === '1' ? 'true' : 'false';
else if (lower === 'current_timestamp' || /^datetime\('now'/.test(lower)) out = 'now()';
else if (lower === 'current_timestamp' || lower === "datetime('now')") out = 'now()';
else if (/^strftime\('%y-%m-%dt%h:%m:%[fs]z?','now'\)$/.test(lower)) out = 'now()';
// datetime('now', '+7 days') and friends keep their modifiers: the function
// rewriter turns them into now() + interval '7 days'.
else out = rewriteFunctions(bare);
if (/gen_random_bytes/.test(out)) ctx.usesPgcrypto = true;
// to_char(...) as a default on a text column is fine; on timestamptz it is not.
Expand Down
10 changes: 10 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ describe('CREATE TABLE', () => {
assert.match(out, /i double precision default 0\.5/i);
assert.match(out, /j bigint default \(extract\(epoch from now\(\)\)::bigint\)/i);
});

test('keeps a literal DEFAULT that is followed by CHECK, and datetime modifiers in a default', () => {
const out = convertSchema(
"create table m (role text not null default 'viewer' check (role in ('owner','viewer')), expires_at text not null default (datetime('now','+7 days')), n integer default 0 check (n >= 0))",
);
assert.match(out, /role text not null default 'viewer' check \(role in \('owner','viewer'\)\)/i);
assert.doesNotMatch(out, /\(check\)/i);
assert.match(out, /expires_at timestamptz not null default \(now\(\) \+ interval '7 days'\)/i);
assert.match(out, /n bigint default 0 check \(n >= 0\)/i);
});
test('a TEXT column with a current-time default is promoted to timestamptz unless told not to', () => {
assert.match(strip(one("create table p (t text default (datetime('now')))")), /t timestamptz/);
assert.match(strip(one("create table p (t text default (datetime('now')))", { promoteTextTimestamps: false })), /t text default now\(\)/i);
Expand Down
Loading