早在2016年,Gal Schlezinger在JavaScript中的Composable SQL上写了一本出色的article。他还介绍了Cuery。我的灵感来自于模板文字中使用功能占位持有人的想法,并希望创建类似的内容,但重点放在半群上,以及一种简单的方法来包括相关数据。这就是RefQL诞生的方式。作为足球的粉丝,我选择了与足球相关的例子来展示图书馆的一些功能。我很高兴与其他开发人员分享此工具,并认为RefQL可以成为任何需要有效且可组合SQL查询的项目的宝贵补充。
助长SQLTag
在功能编程中,Semigroup
是一种具有关联二进制操作的类型,通常由concat
方法表示。使用此方法使您可以创建更合并和可重复使用的代码。在RefQL的Semigroup
植入中,可以通过连接较小的片段来更轻松地构建复杂的查询:
import { Pool } from "pg";
import { sql, Raw } from "refql";
// SQL fragments
const players = sql`
select id, first_name, last_name
from player
`;
const orderBy = sql<{ column: string }>`
order by ${Raw (p => p.column)}
`;
const paginate = sql<{ limit: number; offset: number }>`
limit ${p => p.limit}
offset ${p => p.offset}
`;
// composition by using `concat`
const orderedPlayerPage = players
.concat (orderBy)
.concat (paginate);
// PostgreSQL Pool
const pool = new Pool ({
user: "test",
host: "localhost",
database: "soccer",
password: "test",
port: 5432
});
// necessary in-between piece to make RefQL independent from database clients
const querier = async (query: string, values: any[]) => {
const { rows } = await pool.query (query, values);
return rows;
};
// run
orderedPlayerPage ({ limit: 5, offset: 2, column: "last_name" }, querier).then (console.log);
// 5 players ordered by last_name
// [
// { id: 580, first_name: "Bettie", last_name: "Agresti" },
// { id: 443, first_name: "Roxie", last_name: "Aguilar" },
// { id: 624, first_name: "Louise", last_name: "Aiazzi" },
// { id: 249, first_name: "Rose", last_name: "Alessi" },
// { id: 166, first_name: "Cody", last_name: "Alessi" }
// ];
在上面的代码中,sql
用于创建SQLTag
。 concat
方法将SQL片段players
,orderBy
和paginate
组合到一个可运行的SQLTAG中。要运行它,您需要定义querier
函数。此querier
功能需要为REFQL提供一种运行SQL查询的方法,而不必依赖任何特定的数据库客户端。它应该是一个函数,该函数采用查询字符串和值数组,并返回一个可以解决数据库结果数组的承诺。
输入RQLTag
正如我在介绍中所说的那样,我想创建一个解决方案,以使用模型和关系以模块化和可重复使用的方式轻松附加相关数据。我发现GraphQl使用的声明性语法非常有吸引力,因为它允许开发人员准确指定他们需要的数据以及应如何构造。但是我想更多地专注于构建和构成SQL查询,而无需定义模式和解析器。这使我开发了REFQL,这强调了表之间的参考,并提供了与GraphQl相似的声明语法。我之所以选择RefQl这个名字,是因为它反映了这种关注对参考的关注,以及对我的昵称RAF的点头,它听起来像“ Ref”。让我们重新审视orderedPlayerPage
:
import { Pool } from "pg";
import {
belongsTo, belongsToMany, hasMany,
hasOne, Raw, sql, Table
} from "refql";
const orderBy = sql<{ column: string }>`
order by ${Raw (p => p.column)}
`;
const paginate = sql<{ limit: number; offset: number }>`
limit ${p => p.limit}
offset ${p => p.offset}
`;
// Tables and relationships
const Player = Table ("player", [
belongsTo ("team"),
hasMany ("goal"),
hasOne ("rating"),
belongsToMany ("game")
]);
const Team = Table ("team");
const Goal = Table ("goal");
const Rating = Table ("rating");
const Game = Table ("game");
// Composition by using template literals
const orderedPlayerPage = Player<{ column: string; limit: number; offset: number}>`
id
first_name
last_name
${Team}
${Goal}
${Rating}
${Game}
${orderBy}
${paginate}
`;
const pool = new Pool ({
user: "test",
host: "localhost",
database: "soccer",
password: "test",
port: 5432
});
const querier = async (query: string, values: any[]) => {
const { rows } = await pool.query (query, values);
return rows;
};
orderedPlayerPage ({ limit: 1, offset: 2, column: "last_name" }, querier).then (console.log);
// Full player
// [{
// id: 624,
// first_name: "Louise",
// last_name: "Aiazzi",
// team: { id: 57, name: "FC Idavubut" },
// goals: [{
// id: 2597,
// game_id: 513,
// player_id: 624,
// minute: 54
// }, {
// id: 2598,
// game_id: 513,
// player_id: 624,
// minute: 56
// }],
// rating: {
// player_id: 624, acceleration: 21,
// finishing: 90, positioning: 16,
// shot_power: 4, free_kick: 44,
// stamina: 76, dribbling: 0
// },
// games: [{
// id: 456,
// home_team_id: 51,
// away_team_id: 57,
// result: "2 - 0"
// }, {
// id: 465,
// home_team_id: 52,
// away_team_id: 57,
// result: "2 - 3"
// }]
// }];
此代码将再次检索数据库中的分页和有序的玩家列表,以及他们的相关团队,目标,评分和游戏。这次,Player
用于创建RQLTag
。如您所见,用很少的代码检索大量结构化数据并不需要太多。
输出
使用TypeScript的RefQL的开发通常很痛苦,但是最终结果对图书馆的用户带来了一些好处。用户可以为RQLTAG指定输出类型,这些rqltag在将RQLTAG串联在一起时被组成:
import { Pool } from "pg";
import { belongsTo, sql, Table } from "refql";
// Output types
interface Player {
id: string;
first_name: string;
last_name: string;
}
interface PlayerExtra {
full_name: string;
team: { name: string };
}
const Player = Table ("player", [
belongsTo ("team")
]);
const Team = Table ("team");
// SQLTag
const byId = sql<{ id: number }>`
and id = ${p => p.id}
`;
// RQLTags
const playerById = Player<{ id: number }, Player[]>`
id
first_name
last_name
${byId}
`;
const extraComps = Player<{}, PlayerExtra[]>`
concat:full_name (first_name, " ", last_name)
${Team`name`}
`;
// composition by using `concat`
// new output type: Player[] & PlayerExtra[]
const playerAndTeam = playerById.concat (extraComps);
const pool = new Pool ({
user: "test",
host: "localhost",
database: "soccer",
password: "test",
port: 5432
});
const querier = async (query: string, values: any[]) => {
const { rows } = await pool.query (query, values);
return rows;
};
playerAndTeam ({ id: 1 }, querier).then (console.log);
// [
// {
// id: 1,
// first_name: "Isabel",
// last_name: "Law",
// full_name: "Isabel Law",
// team: { name: "FC Acasaog" }
// }
// ];
准备好生产了吗?
我认为RefQL可以是构建基于SQL的API的强大工具,该工具需要以简单而简单的方式需要复杂的数据检索。虽然这可能不是每个项目或开发人员的理想选择,但是那些喜欢功能性编程方法或更注重SQL的方法来构建API的人可能会发现REFQL是其工具包的宝贵补充。我在一些生产项目中使用REFQL。我在这些项目中编写的疑问不仅容易维护,而且对于我的团队中的其他开发人员来说也很容易理解。