在JavaScript和Typescript中重新访问SQL组成
#typescript #node #database #功能

早在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用于创建SQLTagconcat方法将SQL片段playersorderBypaginate组合到一个可运行的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。我在这些项目中编写的疑问不仅容易维护,而且对于我的团队中的其他开发人员来说也很容易理解。