update
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
- **WHERE**: 条件过滤
|
||||
- **ORDER BY**: 排序
|
||||
- **LIMIT**: 限制结果数量
|
||||
- **操作符**: `=`, `!=`, `>`, `<`, `>=`, `<=`, `IN`, `CONTAINS`
|
||||
- **操作符**: `=`, `!=`, `>`, `<`, `>=`, `<=`, `IN`, `CONTAINS`, `LIKE`
|
||||
- **逻辑**: `AND`, `OR`
|
||||
|
||||
## 语法示例
|
||||
@@ -27,6 +27,11 @@ filter(users, "WHERE metadata.type = 'user' ORDER BY metadata.created_at DESC LI
|
||||
|
||||
// IN 操作
|
||||
filter(users, "WHERE metadata.region IN ['beijing', 'shanghai']");
|
||||
|
||||
// LIKE 操作 (支持 % 匹配任意字符,_ 匹配单个字符)
|
||||
filter(products, "WHERE name LIKE '%Apple%'");
|
||||
filter(products, "WHERE name LIKE 'Apple%'");
|
||||
filter(products, "WHERE name LIKE '%Phone'");
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/js-filter",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "sql like filter for js arrays",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
35
src/index.ts
35
src/index.ts
@@ -1,6 +1,6 @@
|
||||
type Token = {
|
||||
type: 'WHERE' | 'AND' | 'OR' | 'ORDER' | 'BY' | 'ASC' | 'DESC' | 'LIMIT'
|
||||
| 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'IN' | 'CONTAINS'
|
||||
| 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'IN' | 'CONTAINS' | 'LIKE'
|
||||
| 'IDENTIFIER' | 'STRING' | 'NUMBER' | 'COMMA' | 'LBRACKET' | 'RBRACKET'
|
||||
| 'EOF';
|
||||
value: string;
|
||||
@@ -63,7 +63,7 @@ class Lexer {
|
||||
}
|
||||
|
||||
private isKeyword(value: string): boolean {
|
||||
const keywords = ['WHERE', 'AND', 'OR', 'ORDER', 'BY', 'ASC', 'DESC', 'LIMIT', 'IN', 'CONTAINS'];
|
||||
const keywords = ['WHERE', 'AND', 'OR', 'ORDER', 'BY', 'ASC', 'DESC', 'LIMIT', 'IN', 'CONTAINS', 'LIKE'];
|
||||
return keywords.includes(value.toUpperCase());
|
||||
}
|
||||
|
||||
@@ -138,7 +138,8 @@ class Lexer {
|
||||
'DESC': 'DESC',
|
||||
'LIMIT': 'LIMIT',
|
||||
'IN': 'IN',
|
||||
'CONTAINS': 'CONTAINS'
|
||||
'CONTAINS': 'CONTAINS',
|
||||
'LIKE': 'LIKE'
|
||||
};
|
||||
|
||||
if (keywords[upperIdentifier]) {
|
||||
@@ -209,7 +210,11 @@ class Parser {
|
||||
let operator: string;
|
||||
let value: any;
|
||||
|
||||
if (this.currentToken.type === 'CONTAINS') {
|
||||
if (this.currentToken.type === 'LIKE') {
|
||||
operator = 'LIKE';
|
||||
this.eat('LIKE');
|
||||
value = this.parseValue();
|
||||
} else if (this.currentToken.type === 'CONTAINS') {
|
||||
operator = 'CONTAINS';
|
||||
this.eat('CONTAINS');
|
||||
value = this.parseValue();
|
||||
@@ -284,6 +289,23 @@ class Executor {
|
||||
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
||||
}
|
||||
|
||||
private likeToRegex(pattern: string): RegExp {
|
||||
let regex = '';
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
const char = pattern[i];
|
||||
if (char === '%') {
|
||||
regex += '.*';
|
||||
} else if (char === '_') {
|
||||
regex += '.';
|
||||
} else if (/[.*+?^${}()|[\]\\]/.test(char)) {
|
||||
regex += '\\' + char;
|
||||
} else {
|
||||
regex += char;
|
||||
}
|
||||
}
|
||||
return new RegExp(`^${regex}$`, 'i');
|
||||
}
|
||||
|
||||
evaluateCondition(node: ASTNode, item: any): boolean {
|
||||
if (node.type === 'Condition') {
|
||||
const fieldValue = this.getValueByPath(item, node.field!);
|
||||
@@ -307,6 +329,11 @@ class Executor {
|
||||
return Array.isArray(actualValue) && actualValue.includes(fieldValue);
|
||||
case 'CONTAINS':
|
||||
return Array.isArray(fieldValue) && fieldValue.includes(actualValue);
|
||||
case 'LIKE':
|
||||
if (typeof fieldValue !== 'string' || typeof actualValue !== 'string') {
|
||||
return false;
|
||||
}
|
||||
return this.likeToRegex(actualValue).test(fieldValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,3 +53,23 @@ console.log('\n=== 前2个用户,按时间倒序 ===');
|
||||
const limitResult = filter(users, "ORDER BY metadata.created_at DESC LIMIT 2");
|
||||
console.log('Limit result length:', limitResult.length);
|
||||
console.log(limitResult);
|
||||
|
||||
const products = [
|
||||
{ name: 'Apple iPhone 15', category: 'Phone' },
|
||||
{ name: 'Samsung Galaxy S24', category: 'Phone' },
|
||||
{ name: 'Apple MacBook Pro', category: 'Laptop' },
|
||||
{ name: 'Apple Watch', category: 'Watch' }
|
||||
];
|
||||
|
||||
console.log('\n=== LIKE 测试 ===');
|
||||
console.log('\n=== 包含 Apple 的产品 (%Apple%) ===');
|
||||
console.log(filter(products, "WHERE name LIKE '%Apple%'"));
|
||||
|
||||
console.log('\n=== 以 Apple 开头的产品 (Apple%) ===');
|
||||
console.log(filter(products, "WHERE name LIKE 'Apple%'"));
|
||||
|
||||
console.log('\n=== 以 Phone 结尾的产品 (%Phone) ===');
|
||||
console.log(filter(products, "WHERE name LIKE '%Phone%'"));
|
||||
|
||||
console.log('\n=== 第二个字符任意的产品 (A_pple%) ===');
|
||||
console.log(filter(products, "WHERE name LIKE 'A_pple%'"));
|
||||
|
||||
Reference in New Issue
Block a user