From ae4fcacd9dbe1f1a0eb3f98864c835952cc16648 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 31 Dec 2025 13:11:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20NOT=20LIKE=E3=80=81IS=20NU?= =?UTF-8?q?LL=20=E5=92=8C=20IS=20NOT=20NULL=20=E6=93=8D=E4=BD=9C=E7=AC=A6?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 +++++++++++++++++++++++++++++++++++++++- package.json | 4 ++-- src/index.ts | 40 +++++++++++++++++++++++++++++++++++----- test/example.ts | 21 +++++++++++++++++++++ 4 files changed, 97 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7cb9acc..38acd92 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - **WHERE**: 条件过滤 - **ORDER BY**: 排序 - **LIMIT**: 限制结果数量 -- **操作符**: `=`, `!=`, `>`, `<`, `>=`, `<=`, `IN`, `CONTAINS`, `LIKE` +- **操作符**: `=`, `!=`, `>`, `<`, `>=`, `<=`, `IN`, `CONTAINS`, `LIKE`, `NOT LIKE`, `IS NULL`, `IS NOT NULL` - **逻辑**: `AND`, `OR` ## 语法示例 @@ -32,6 +32,15 @@ filter(users, "WHERE metadata.region IN ['beijing', 'shanghai']"); filter(products, "WHERE name LIKE '%Apple%'"); filter(products, "WHERE name LIKE 'Apple%'"); filter(products, "WHERE name LIKE '%Phone'"); + +// NOT LIKE 操作 (排除匹配模式的项) +filter(products, "WHERE name NOT LIKE '%Apple%'"); + +// IS NULL 操作 (匹配 null 或 undefined 的值) +filter(users, "WHERE email IS NULL"); + +// IS NOT NULL 操作 (匹配非 null 且非 undefined 的值) +filter(users, "WHERE email IS NOT NULL"); ``` ## 使用方法 @@ -72,3 +81,32 @@ const premiumUsers = filter(users, "WHERE metadata.tags CONTAINS 'premium'"); // 查找北京的用户,按创建时间倒序 const beijingUsers = filter(users, "WHERE metadata.region = 'beijing' ORDER BY metadata.created_at DESC"); ``` + +## 新增操作符说明 + +### NOT LIKE +排除匹配指定模式的数据项。 + +```javascript +// 查找不包含 "Apple" 的产品 +const nonAppleProducts = filter(products, "WHERE name NOT LIKE '%Apple%'"); +``` + +### IS NULL / IS NOT NULL +检查字段值是否为 `null` 或 `undefined`。 + +```javascript +const usersWithNullEmail = [ + { name: 'Alice', email: 'alice@example.com', age: 25 }, + { name: 'Bob', email: null, age: 30 }, + { name: 'Charlie', email: undefined, age: 35 }, +]; + +// 查找 email 为 null 或 undefined 的用户 +const usersWithoutEmail = filter(usersWithNullEmail, "WHERE email IS NULL"); + +// 查找 email 不为 null 且不为 undefined 的用户 +const usersWithEmail = filter(usersWithNullEmail, "WHERE email IS NOT NULL"); +``` + +**注意**: `IS NULL` 和 `IS NOT NULL` 会同时检查 JavaScript 的 `null` 和 `undefined` 值。 diff --git a/package.json b/package.json index e95cfe5..ffc56e4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@kevisual/js-filter", - "version": "0.0.2", - "description": "sql like filter for js arrays", + "version": "0.0.3", + "description": "用于 JavaScript 数组的 SQL LIKE 过滤器函数", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { diff --git a/src/index.ts b/src/index.ts index 645b0d0..d7e5246 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ type Token = { type: 'WHERE' | 'AND' | 'OR' | 'ORDER' | 'BY' | 'ASC' | 'DESC' | 'LIMIT' - | 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'IN' | 'CONTAINS' | 'LIKE' - | 'IDENTIFIER' | 'STRING' | 'NUMBER' | 'COMMA' | 'LBRACKET' | 'RBRACKET' + | 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'IN' | 'CONTAINS' | 'LIKE' | 'NOT' + | 'IS' | 'NULL' | 'IDENTIFIER' | 'STRING' | 'NUMBER' | 'COMMA' | 'LBRACKET' | 'RBRACKET' | 'EOF'; value: string; pos: number; @@ -63,7 +63,7 @@ class Lexer { } private isKeyword(value: string): boolean { - const keywords = ['WHERE', 'AND', 'OR', 'ORDER', 'BY', 'ASC', 'DESC', 'LIMIT', 'IN', 'CONTAINS', 'LIKE']; + const keywords = ['WHERE', 'AND', 'OR', 'ORDER', 'BY', 'ASC', 'DESC', 'LIMIT', 'IN', 'CONTAINS', 'LIKE', 'NOT', 'IS', 'NULL']; return keywords.includes(value.toUpperCase()); } @@ -139,7 +139,10 @@ class Lexer { 'LIMIT': 'LIMIT', 'IN': 'IN', 'CONTAINS': 'CONTAINS', - 'LIKE': 'LIKE' + 'LIKE': 'LIKE', + 'NOT': 'NOT', + 'IS': 'IS', + 'NULL': 'NULL' }; if (keywords[upperIdentifier]) { @@ -210,10 +213,26 @@ class Parser { let operator: string; let value: any; - if (this.currentToken.type === 'LIKE') { + if (this.currentToken.type === 'NOT') { + this.eat('NOT'); + operator = 'NOT LIKE'; + this.eat('LIKE'); + value = this.parseValue(); + } else if (this.currentToken.type === 'LIKE') { operator = 'LIKE'; this.eat('LIKE'); value = this.parseValue(); + } else if (this.currentToken.type === 'IS') { + this.eat('IS'); + if ((this.currentToken.type as any) === 'NOT') { + operator = 'IS NOT NULL'; + this.eat('NOT'); + this.eat('NULL'); + } else { + operator = 'IS NULL'; + this.eat('NULL'); + } + value = null; } else if (this.currentToken.type === 'CONTAINS') { operator = 'CONTAINS'; this.eat('CONTAINS'); @@ -232,6 +251,8 @@ class Parser { return { type: 'Condition', field, operator, value }; } + + private parseInList(): any[] { this.eat('LBRACKET'); const values: any[] = []; @@ -334,6 +355,15 @@ class Executor { return false; } return this.likeToRegex(actualValue).test(fieldValue); + case 'NOT LIKE': + if (typeof fieldValue !== 'string' || typeof actualValue !== 'string') { + return true; + } + return !this.likeToRegex(actualValue).test(fieldValue); + case 'IS NULL': + return fieldValue === null || fieldValue === undefined; + case 'IS NOT NULL': + return fieldValue !== null && fieldValue !== undefined; default: return false; } diff --git a/test/example.ts b/test/example.ts index 33f7e7a..4295466 100644 --- a/test/example.ts +++ b/test/example.ts @@ -73,3 +73,24 @@ console.log(filter(products, "WHERE name LIKE '%Phone%'")); console.log('\n=== 第二个字符任意的产品 (A_pple%) ==='); console.log(filter(products, "WHERE name LIKE 'A_pple%'")); + +console.log('\n=== NOT LIKE 测试 ==='); +console.log('\n=== 不包含 Apple 的产品 (NOT LIKE %Apple%) ==='); +console.log(filter(products, "WHERE name NOT LIKE '%Apple%'")); + +console.log('\n=== IS NULL 和 IS NOT NULL 测试 ==='); +const usersWithNull = [ + { name: 'Alice', email: 'alice@example.com', age: 25 }, + { name: 'Bob', email: null, age: 30 }, + { name: 'Charlie', email: undefined, age: 35 }, + { name: 'Diana', email: 'diana@example.com', age: null } +]; + +console.log('\n=== Email 为 NULL 或 undefined 的用户 ==='); +console.log(filter(usersWithNull, "WHERE email IS NULL")); + +console.log('\n=== Email 不为 NULL 且不为 undefined 的用户 ==='); +console.log(filter(usersWithNull, "WHERE email IS NOT NULL")); + +console.log('\n=== Age 不为 NULL 且不为 undefined 的用户 ==='); +console.log(filter(usersWithNull, "WHERE age IS NOT NULL"));