Qustar
Qustar,它不仅仅是一个工具,更像是一位贴心的老朋友,懂你的需求,解你的难题。它用那简洁的 API,让我们的代码变得优雅;用那强大的功能,让我们的操作变得高效。无论是 PostgreSQL、SQLite、MySQL 还是 MariaDB,它都能游刃有余,就像是我们手中的万能钥匙,打开一个又一个数据的宝箱。
特性
✅ 表达性强且高级的查询构建器
✅ 支持 TypeScript
✅ SQL 数据库:
✅ PostgreSQL
✅ SQLite
✅ MySQL
✅ MariaDB
⬜ SQL Server
⬜ Oracle
✅ 导航属性
✅ 无需代码生成
✅ 无惊喜,所有查询产生 100% SQL
✅ 原生 SQL
⬜ 迁移
⬜ 事务
快速开始
要开始使用 qustar 与 PostgreSQL(所有支持的数据源列表见下文 #supported-database-drivers[1]),请运行以下命令:
npm install qustar qustar-pg pg
以下是 qustar 的示例用法:
import {PgConnector} from 'qustar-pg';
import {Q} from 'qustar';
// 指定一个模式
const users = Q.table({
name: 'users',
schema: {
// 插入时不需要生成
id: Q.i32().generated(), // 32 位整数
firstName: Q.string(), // 任意文本
lastName: Q.string(),
age: Q.i32().null(), // 可为空的整数
},
});
// 组合一个查询
const query = users
.orderByDesc(user => user.createdAt)
// map 将被翻译成 100% SQL,就像其他所有操作一样
.map(user => ({
name: user.firstName.concat(' ', user.lastName),
age: user.age,
}))
.limit(3);
// 连接到你的数据库
const connector = new PgConnector('postgresql://qustar:passwd@localhost:5432');
// 运行查询
console.log('users:', await query.fetch(connector));
输出:
{ age: 54, name: 'Linus Torvalds' }
{ age: 29, name: 'Clark Kent' }
{ age: 18, name: 'John Smith' }
上述查询将被翻译为:
SELECT
"s1"."age",
concat("s1"."firstName", ' ', "s1"."lastName") AS "name"
FROM
users AS "s1"
ORDER BY
("s1"."createdAt") DESC
LIMIT
3
插入/更新/删除:
// 插入
await users.insert({firstName: 'New', lastName: 'User'}).execute(connector);
// 更新
await users
.filter(user => user.id.eq(42))
.update(user => ({age: user.age.add(1)}))
.execute(connector);
// 删除
await users.delete(user => user.id.eq(42)).execute(connector);
支持的数据库驱动
要对数据库执行查询,你需要一个 _connector_。有许多现成的连接器封装了现有的 NodeJS 驱动:
PostgreSQL qustar-pg[2] SQLite qustar-better-sqlite3[3] (推荐) qustar-sqlite3[4] MySQL qustar-mysql2[5] MariaDB qustar-mysql2[6]
如果你实现了自己的连接器,请告诉我,我会将其添加到上面的列表中!
使用
任何查询都从一个表或 原生 SQL[7] 开始。我们稍后会更多地讨论原生查询,现在基本用法如下:
import {Q} from 'qustar';
const users = Q.table({
name: 'users',
schema: {
id: Q.i32(),
age: Q.i32().null(),
// ...
},
});
在 qustar 中,你通过调用查询方法如 .filter
或 .map
来组合查询:
const young = users.filter(user => user.age.lt(18));
const youngIds = young.map(user => user.id);
// 或者
const ids = users.filter(user => user.age.lt(18)).map(user => user.id);
查询是不可变的,因此你可以安全地重复使用它们。
对于像 .filter
或 .map
这样的方法,你传递一个回调,它返回一个 _表达式_。表达式表示你希望执行的条件或操作。表达式是使用 .add
或 .eq
等方法构建的:
// 对于数组,你会这样写:users.filter(x => x.age + 1 === x.height - 5)
const a = users.filter(user => user.age.add(1).eq(user.height.sub(5)));
// 你也可以使用 Q.eq 来实现相同的效果
import {Q} from 'qustar';
const b = users.map(user => Q.eq(user.age.add(1), user.height.sub(5)));
我们不能使用原生运算符如 +
或 ===
,因为 JavaScript 不支持运算符重载。你可以在 这里[8] 找到支持的表达式操作的完整列表。
现在让我们谈谈查询和表达式。
查询
.filter(条件)
const adults = users
// 年龄 >= 18 的用户
.filter(user => /* 任何表达式 */ user.age.gte(18));
.map(映射器)
const userIds = users.map(user => user.id);
const user = users
// 你可以映射到一个对象
.map(user => ({id: user.id, name: user.name}));
const userInfo = users
// 你可以映射到嵌套对象
.map(user => ({
id: user.id,
info: {
adult: user.age.gte(18),
nameLength: user.name.length(),
},
}));
.orderByDesc(选择器), .orderByAsc(选择器)
const users = users
// 按年龄升序排序
.orderByAsc(user => user.age)
// 然后按名字降序排序
.thenByDesc(user => user.name);
.drop(计数), Query.limit(计数)
const users = users
.orderByAsc(user => user.id)
// 跳过前十个用户
.drop(10)
// 然后只取五个
.limit(5);
.slice(开始, 结束)
你也可以使用 .slice
方法来实现相同的效果:
const users = users
// 开始 = 10, 结束 = 15
.slice(10, 15);
.{inner,left,right}Join(选项)
Qustar 支持 .innerJoin
, .leftJoin
, .rightJoin
和 .fullJoin
:
const bobPosts = posts
.innerJoin({
right: users,
condition: (post, user) => post.authorId.eq(user.id),
select: (post, author) => ({
text: post.text,
author: author.name,
}),
})
.filter(({author}) => author.like('bob%'));
.unique()
你可以使用 .unique
方法选择不同的行:
const names = users.map(user => user.name).unique();
.groupBy(选项)
const stats = users.groupBy({
by: user => user.age,
select: user => ({
age: user.age,
count: Expr.count(1),
averageTax: user.salary.mul(user.taxRate).mean(),
}),
});
.union(查询)
const studentNames = students.map(student => student.name);
const teacherNames = teachers.map(teacher => teacher.name);
const uniqueNames = studentNames.union(teacherNames);
.unionAll(查询)
const studentNames = students.map(student => student.name);
const teacherNames = teachers.map(teacher => teacher.name);
const peopleCount = studentNames.unionAll(teacherNames).count();
.concat(查询)
const studentNames = students.map(student => student.name);
const teacherNames = teachers.map(teacher => teacher.name);
// concat 保留原始排序
const allNames = studentNames.concat(teacherNames);
.intersect(查询)
const studentNames = students.map(student => student.name);
const teacherNames = teachers.map(teacher => teacher.name);
const studentAndTeacherNames = studentNames.intersect(teacherNames);
.except(查询)
const studentNames = students.map(student => student.name);
const teacherNames = teachers.map(teacher => teacher.name);
const studentOnlyNames = studentNames.except(teacherNames);
.flatMap(映射器)
const postsWithAuthor = users.flatMap(user =>
posts
.filter(post => post.authorId.eq(user.id))
.map(post => ({text: post.text, author: user.name}))
);
.includes(值)
const userExists = users.map(user => user.id).includes(42);
模式
支持的列类型列表:
boolean: 真或假 i8: 8 位整数 i16: 16 位整数 i32: 32 位整数 i64: 64 位整数 f32: 32 位浮点数 f64: 64 位浮点数 string: 可变长度字符串
原生 SQL
你可以像这样使用原生 SQL:
import {Q, sql} from 'qustar';
const users = Q.rawQuery({
sql: sql`SELECT * from users`,
// 我们必须指定模式,这样 qustar 才知道如何组合查询
schema: {
id: Q.i32(),
age: Q.i32().null(),
},
})
.filter(user => user.age.lte(25))
.map(user => user.id);
你也可以在嵌套查询中使用别名,像这样:
const postIds = users.flatMap(user =>
Q.rawQuery({
sql: sql`
SELECT
id
FROM
posts p
WHERE p.authorId = ${user.id}'
})`,
schema: {
id: Q.i32(),
},
});
);
你可以使用 Q.rawExpr
在操作的一部分中使用原生 SQL:
const halfIds = users.map(user => ({
halfId: Q.rawExpr({sql: sql`CAST(${user.id} as REAL) / 2`, schema: Q.f32()}),
name: user.name,
}));
上述查询将被翻译为:
SELECT
"s1"."name",
(CAST(("s1"."id") as REAL) / 2) AS "halfId"
FROM
users AS "s1"
许可证
MIT 许可证,见 LICENSE
。
结语
记住,每一次代码的编写,都是一次新的探险。Qustar 将是你的罗盘,指引你穿越 SQL 的迷宫,找到那些隐藏的数据宝藏。而你,就是那位勇敢的探险家,用智慧和勇气,将这些宝藏转化为应用程序中的黄金。
所以,当你下次面对着满屏的数据库查询,感到手足无措时,别忘了你不是一个人在战斗。Qustar 就在你身边,像一位老朋友,默默地支持你,陪你一起笑对那些复杂的查询和棘手的 bug。
最后,愿你的代码永远没有 bug,愿你的查询永远高效,愿你的项目永远成功。如果你觉得 Qustar 还不错,别忘了推荐给你的小伙伴们,毕竟,好东西要一起分享嘛!再见啦,愿你在代码的海洋中,乘风破浪,一帆风顺!
#supported-database-drivers: #supported-database-drivers
[2]qustar-pg: https://www.npmjs.com/package/qustar-pg
[3]qustar-better-sqlite3: https://www.npmjs.com/package/qustar-better-sqlite3
[4]qustar-sqlite3: https://www.npmjs.com/package/qustar-sqlite3
[5]qustar-mysql2: https://www.npmjs.com/package/qustar-mysql2
[6]qustar-mysql2: https://www.npmjs.com/package/qustar-mysql2
[7]原生 SQL: #raw-sql
[8]这里: #expressions