From b062db903f45b3e7950a5be32d2395a0d1cb1382 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Thu, 3 Sep 2026 12:31:58 -0700 Subject: [PATCH 1/2] fix(d1): preserve unary operators in processCol to support leading + and - --- d1.js | 10 ++++++++- package-lock.json | 28 +----------------------- test/test_order.test.js | 47 +++++++++++++++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/d1.js b/d1.js index d81183b..3131d0e 100644 --- a/d1.js +++ b/d1.js @@ -349,8 +349,16 @@ export class D1 { } processCol(col, knownTables = [], prefix = null) { + if (typeof col !== 'string') return col let q0 = col + // Preserve leading unary operators (e.g. '+threads.showAt' or '-threads.score') + let unary = '' + if ((q0.startsWith('+') || q0.startsWith('-')) && q0.length > 1) { + unary = q0[0] + q0 = q0.slice(1) + } + // Auto prefix if (prefix && !(q0.includes('$') || q0.includes('('))) { if (!q0.includes('.')) { @@ -378,7 +386,7 @@ export class D1 { q0 = `json_extract(${split[0]}, '$.${split.slice(1).join('.')}')` } } - return q0 + return unary + q0 } singleW(q2, knownTables = [], prefix = null) { diff --git a/package-lock.json b/package-lock.json index bc4698f..bfdfccf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "api": "github:treeder/api#semver:^1", "dotenv": "^17.4.2", + "esbuild": "^0.28.1", "jsonc-parse": "^2.0.0", "models": "github:treeder/models#semver:^1", "nanoid": "^6.0.1" @@ -168,7 +169,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -185,7 +185,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -202,7 +201,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -219,7 +217,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -236,7 +233,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -253,7 +249,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -270,7 +265,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -287,7 +281,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -304,7 +297,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -321,7 +313,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -338,7 +329,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -355,7 +345,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -372,7 +361,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -389,7 +377,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -406,7 +393,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -423,7 +409,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -440,7 +425,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -457,7 +441,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -474,7 +457,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -491,7 +473,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -508,7 +489,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -525,7 +505,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -542,7 +521,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -559,7 +537,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -576,7 +553,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -593,7 +569,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1795,7 +1770,6 @@ "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { diff --git a/test/test_order.test.js b/test/test_order.test.js index 811b493..130e287 100644 --- a/test/test_order.test.js +++ b/test/test_order.test.js @@ -77,10 +77,45 @@ test('test multiple order clauses', async () => { ], }, }) - expect(r4.users).toBeDefined() - expect(r4.users.length).toBe(4) - expect(r4.users[0].email).toBe(`order_b2_${timestamp}@example.com`) - expect(r4.users[1].email).toBe(`order_a1_${timestamp}@example.com`) - expect(r4.users[2].email).toBe(`order_b1_${timestamp}@example.com`) - expect(r4.users[3].email).toBe(`order_a2_${timestamp}@example.com`) + // 5. Test unary + prefix ordering with table qualifier and without + let r5 = await c.api.fetch(`/v1/users/query`, { + method: 'POST', + body: { + where: [['email', 'LIKE', `order_%_${timestamp}@example.com`]], + order: ['+users.name', 'asc'], + }, + }) + expect(r5.users).toBeDefined() + expect(r5.users.length).toBe(4) + expect(r5.users[0].email).toBe(`order_a1_${timestamp}@example.com`) + expect(r5.users[1].email).toBe(`order_a2_${timestamp}@example.com`) + + // 6. Test expression with parentheses + let r6 = await c.api.fetch(`/v1/users/query`, { + method: 'POST', + body: { + where: [['email', 'LIKE', `order_%_${timestamp}@example.com`]], + order: ['(+users.name)', 'asc'], + }, + }) + expect(r6.users).toBeDefined() + expect(r6.users.length).toBe(4) + expect(r6.users[0].email).toBe(`order_a1_${timestamp}@example.com`) + expect(r6.users[1].email).toBe(`order_a2_${timestamp}@example.com`) +}) + +test('test processCol with unary operators', async () => { + const { D1 } = await import('../d1.js') + const d1 = new D1({}) + + // Qualified table with unary + + expect(d1.processCol('+threads.showAt', ['threads'], 'threads')).toBe('+threads.showAt') + // Unqualified column with unary + and prefix + expect(d1.processCol('+showAt', ['threads'], 'threads')).toBe('+threads.showAt') + // Qualified table with unary - + expect(d1.processCol('-threads.showAt', ['threads'], 'threads')).toBe('-threads.showAt') + // Parenthesized expression + expect(d1.processCol('(+threads.showAt)', ['threads'], 'threads')).toBe('(+threads.showAt)') + // JSON subfield with unary + + expect(d1.processCol('+data.priority', ['users'], 'users')).toBe("+json_extract(users.data, '$.priority')") }) From 5833ec2dc5a668afffe3228b7f5c058e2c58d219 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Thu, 3 Sep 2026 12:37:50 -0700 Subject: [PATCH 2/2] Update test/test_order.test.js Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/test_order.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_order.test.js b/test/test_order.test.js index 130e287..b06fd0e 100644 --- a/test/test_order.test.js +++ b/test/test_order.test.js @@ -77,6 +77,13 @@ test('test multiple order clauses', async () => { ], }, }) + expect(r4.users).toBeDefined() + expect(r4.users.length).toBe(4) + expect(r4.users[0].email).toBe(`order_b2_${timestamp}@example.com`) + expect(r4.users[1].email).toBe(`order_a1_${timestamp}@example.com`) + expect(r4.users[2].email).toBe(`order_b1_${timestamp}@example.com`) + expect(r4.users[3].email).toBe(`order_a2_${timestamp}@example.com`) + // 5. Test unary + prefix ordering with table qualifier and without let r5 = await c.api.fetch(`/v1/users/query`, { method: 'POST',