diff --git a/README.md b/README.md index d117509..4954fd1 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ const tx = await db.transaction('write'); await tx.execute(...); await tx.commit | `lower(hex(randomblob(16)))`, `hex(randomblob(n))` | `encode(gen_random_bytes(n), 'hex')` | | `group_concat(x, sep)` | `string_agg(x::text, sep)` | | `ifnull(a, b)` | `coalesce(a, b)` | +| `x LIKE y` | `x ILIKE y` (SQLite's LIKE is case-insensitive; `NOT LIKE` and `ESCAPE` kept) | | `CAST(x AS INTEGER)` | `CAST(x AS BIGINT)` (SQLite's integer is 64-bit) | | `` `backticked` `` identifiers | `"quoted"` | | `PRAGMA ...` | no-op, empty result | diff --git a/package-lock.json b/package-lock.json index fa45a14..5529151 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@profullstack/libsql-pg", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@profullstack/libsql-pg", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "dependencies": { "@profullstack/libsql-pg": "^0.1.1", diff --git a/package.json b/package.json index d5efd5e..cf8cf23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@profullstack/libsql-pg", - "version": "0.1.3", + "version": "0.1.4", "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": [ diff --git a/src/rewrite.js b/src/rewrite.js index 05fc1df..f5c0cf0 100644 --- a/src/rewrite.js +++ b/src/rewrite.js @@ -430,6 +430,9 @@ export function rewriteFunctions(sql) { return `string_agg(${distinct ? 'distinct ' : ''}(${first})::text, ${sep})`; }); sql = replaceCode(sql, /\bifnull\s*\(/gi, 'coalesce('); + // SQLite's LIKE is case-insensitive (ASCII); Postgres's is not. ILIKE keeps the + // meaning every LIKE in a SQLite app relied on (NOT LIKE -> NOT ILIKE, ESCAPE kept). + sql = replaceCode(sql, /\blike\b/gi, 'ILIKE'); sql = replaceCalls(sql, 'instr', ({ args }) => (args.length === 2 ? `position(${args[1].trim()} in ${args[0].trim()})` : null)); sql = replaceCalls(sql, 'max', ({ args }) => (args.length >= 2 ? `greatest(${args.map((a) => a.trim()).join(', ')})` : null)); sql = replaceCalls(sql, 'min', ({ args }) => (args.length >= 2 ? `least(${args.map((a) => a.trim()).join(', ')})` : null)); diff --git a/test/rewrite.test.js b/test/rewrite.test.js index fee7aee..1a1f635 100644 --- a/test/rewrite.test.js +++ b/test/rewrite.test.js @@ -163,6 +163,10 @@ describe('ids, strings and aggregates', () => { assert.equal(sql("select group_concat(name, ' | ') from t"), "select string_agg((name)::text, ' | ') from t"); assert.equal(sql("select group_concat(distinct name, ', ') from t"), "select string_agg(distinct (name)::text, ', ') from t"); }); + test('LIKE -> ILIKE (SQLite LIKE is case-insensitive), literals untouched', () => { + assert.equal(sql("select 1 from t where a like ? and b not like 'x%' escape '\\'"), "select 1 from t where a ILIKE ? and b not ILIKE 'x%' escape '\\'"); + assert.equal(sql("select 'I like it' from t where c ilike ?"), "select 'I like it' from t where c ilike ?"); + }); test('ifnull -> coalesce, instr -> position', () => { assert.equal(sql('select ifnull(a, b), instr(a, b) from t'), 'select coalesce(a, b), position(b in a) from t'); }); @@ -178,9 +182,9 @@ describe('ids, strings and aggregates', () => { test('REGEXP -> ~', () => { assert.equal(sql("select * from t where a regexp '^x'"), "select * from t where a ~ '^x'"); }); - test('LIMIT -1 -> LIMIT ALL; LIMIT ? OFFSET ? untouched; LIKE untouched', () => { + test('LIMIT -1 -> LIMIT ALL; LIMIT ? OFFSET ? untouched', () => { assert.equal(sql('select * from t limit -1'), 'select * from t LIMIT ALL'); - assert.equal(sql("select * from t where a like ? limit ? offset ?"), "select * from t where a like ? limit ? offset ?"); + assert.equal(sql("select * from t where a like ? limit ? offset ?"), "select * from t where a ILIKE ? limit ? offset ?"); }); test('INDEXED BY hints are dropped', () => { assert.equal(sql('select * from t indexed by t_idx where a = ?'), 'select * from t where a = ?');