摘要:引言前端精讀手寫編譯器系列介紹了如何利用生成語法樹,而還有一些庫的作用是根據(jù)語法樹生成語句。對,有利就有弊,這些庫不遵循語法樹,但利用簡化的對象模型快速生成,使得代碼抽象程度得到了提高。
1 引言
前端精讀《手寫 SQL 編譯器系列》 介紹了如何利用 SQL 生成語法樹,而還有一些庫的作用是根據(jù)語法樹生成 SQL 語句。
除此之外,還有一種庫,是根據(jù)編程語言生成 SQL。sqorn 就是一個(gè)這樣的庫。
可能有人會(huì)問,利用編程語言生成 SQL 有什么意義?既沒有語法樹規(guī)范,也不如直接寫 SQL 通用。對,有利就有弊,這些庫不遵循語法樹,但利用簡化的對象模型快速生成 SQL,使得代碼抽象程度得到了提高。而代碼抽象程度得到提高,第一個(gè)好處就是易讀,第二個(gè)好處就是易操作。
數(shù)據(jù)庫特別容易抽象為面向?qū)ο竽P停鴮?shù)據(jù)庫的操作語句 - SQL 是一種結(jié)構(gòu)化查詢語句,只能描述一段一段的查詢,而面向?qū)ο竽P蛥s適合描述一個(gè)整體,將數(shù)據(jù)庫多張表串聯(lián)起來。
舉個(gè)例子,利用 typeorm,我們可以用 a 與 b 兩個(gè) Class 描述兩張表,同時(shí)利用 ManyToMany 裝飾器分別修飾 a 與 b 的兩個(gè)字段,將其建立起 多對多的關(guān)聯(lián),而這個(gè)映射到 SQL 結(jié)構(gòu)是三張表,還有一張是中間表 ab,以及查詢時(shí)涉及到的 left join 操作,而在 typeorm 中,一條 find 語句就能連帶查詢處多對多關(guān)聯(lián)關(guān)系。
這就是這種利用編程語言生成 SQL 庫的價(jià)值,所以本周我們分析一下 sqorn 這個(gè)庫的源碼,看看利用對象模型生成 SQL 需要哪些步驟。
2 概述我們先看一下 sqorn 的語法。
const sq = require("sqorn-pg")(); const Person = sq`person`, Book = sq`book`; // SELECT const children = await Person`age < ${13}`; // "select * from person where age < 13" // DELETE const [deleted] = await Book.delete({ id: 7 })`title`; // "delete from book where id = 7 returning title" // INSERT await Person.insert({ firstName: "Rob" }); // "insert into person (first_name) values ("Rob")" // UPDATE await Person({ id: 23 }).set({ name: "Rob" }); // "update person set name = "Rob" where id = 23"
首先第一行的 sqorn-pg 告訴我們 sqorn 按照 SQL 類型拆成不同分類的小包,這是因?yàn)椴煌瑪?shù)據(jù)庫支持的方言不同,sqorn 希望在語法上抹平數(shù)據(jù)庫間差異。
其次 sqorn 也是利用面向?qū)ο笏季S的,上面的例子通過 sq`person` 生成了 Person 實(shí)例,實(shí)際上也對應(yīng)了 person 表,然后 Person`age < ${13}` 表示查詢:select * from person where age < 13
上面是利用 ES6 模板字符串的功能實(shí)現(xiàn)的簡化 where 查詢功能,sqorn 主要還是利用一些函數(shù)完成 SQL 語句生成,比如 where delete insert 等等,比較典型的是下面的 Example:
sq.from`book`.return`distinct author` .where({ genre: "Fantasy" }) .where({ language: "French" }); // select distinct author from book // where language = "French" and genre = "Fantsy"
所以我們閱讀 sqorn 源碼,探討如何利用實(shí)現(xiàn)上面的功能。
3 精讀我們從四個(gè)方面入手,講明白 sqorn 的源碼是如何組織的,以及如何滿足上面功能的。
方言為了實(shí)現(xiàn)各種 SQL 方言,需要在實(shí)現(xiàn)功能之前,將代碼拆分為內(nèi)核代碼與拓展代碼。
內(nèi)核代碼就是 sqorn-sql 而拓展代碼就是 sqorn-pg,拓展代碼自身只要實(shí)現(xiàn) pg 數(shù)據(jù)庫自身的特殊邏輯, 加上 sqorn-sql 提供的核心能力,就能形成完整的 pg SQL 生成功能。
實(shí)現(xiàn)數(shù)據(jù)庫連接
sqorn 不但生成 query 語句,也會(huì)參與數(shù)據(jù)庫連接與運(yùn)行,因此方言庫的一個(gè)重要功能就是做數(shù)據(jù)庫連接。sqorn 利用 pg 這個(gè)庫實(shí)現(xiàn)了連接池、斷開、查詢、事務(wù)的功能。
覆寫接口函數(shù)
內(nèi)核代碼想要具有拓展能力,暴露出一些接口讓 sqorn-xx 覆寫是很基本的。
context內(nèi)核代碼中,最重要的就是 context 屬性,因?yàn)槿祟惲?xí)慣一步一步寫代碼,而最終生成的 query 語句是連貫的,所以這個(gè)上下文對象通過 updateContext 存儲(chǔ)了每一條信息:
{ name: "limit", updateContext: (ctx, args) => { ctx.lim = args } } { name: "where", updateContext: (ctx, args) => { ctx.whr.push(args) } }
比如 Person.where({ name: "bob" }) 就會(huì)調(diào)用 ctx.whr.push({ name: "bob" }),因?yàn)?where 條件是個(gè)數(shù)組,因此這里用 push,而 limit 一般僅有一個(gè),所以 context 對 lim 對象的存儲(chǔ)僅有一條。
其他操作諸如 where delete insert with from 都會(huì)類似轉(zhuǎn)化為 updateContext,最終更新到 context 中。
創(chuàng)建 builder不用太關(guān)心下面的 sqorn-xx 包名細(xì)節(jié),這一節(jié)主要目的是說明如何實(shí)現(xiàn) Demo 中的鏈?zhǔn)秸{(diào)用,至于哪個(gè)模塊放在哪并不重要(如果要自己造輪子就要仔細(xì)學(xué)習(xí)一下作者的命名方式)。
在 sqorn-core 代碼中創(chuàng)建了 builder 對象,將 sqorn-sql 中創(chuàng)建的 methods merge 到其中,因此我們可以使用 sq.where 這種語法。而為什么可以 sq.where().limit() 這樣連續(xù)調(diào)用呢?可以看下面的代碼:
for (const method of methods) { // add function call methods builder[name] = function(...args) { return this.create({ name, args, prev: this.method }); }; }
這里將 where delete insert with from 等 methods merge 到 builder 對象中,且當(dāng)其執(zhí)行完后,通過 this.create() 返回一個(gè)新 builder,從而完成了鏈?zhǔn)秸{(diào)用功能。
生成 query上面三點(diǎn)講清楚了如何支持方言、用戶代碼內(nèi)容都收集到 context 中了,而且我們還創(chuàng)建了可以鏈?zhǔn)秸{(diào)用的 builder 對象方便用戶調(diào)用,那么只剩最后一步了,就是生成 query。
為了利用 context 生成 query,我們需要對每個(gè) key 編寫對應(yīng)的函數(shù)做處理,拿 limit 舉例:
export default ctx => { if (!ctx.lim) return; const txt = build(ctx, ctx.lim); return txt && `limit ${txt}`; };
從 context.lim 拿取 limit 配置,組合成 limit xxx 的字符串并返回就可以了。
build 函數(shù)是個(gè)工具函數(shù),如果 ctx.lim 是個(gè)數(shù)組,就會(huì)用逗號(hào)拼接。
大部分操作比如 delete from having 都做這么簡單的處理即可,但像 where 會(huì)相對復(fù)雜,因?yàn)閮?nèi)部包含了 condition 子語法,注意用 and 拼接即可。
最后是順序,也需要在代碼中確定:
export default { sql: query(sql), select: query(wth, select, from, where, group, having, order, limit, offset), delete: query(wth, del, where, returning), insert: query(wth, insert, value, returning), update: query(wth, update, set, where, returning) };
這個(gè)意思是,一個(gè) select 語句會(huì)通過 wth, select, from, where, group, having, order, limit, offset 的順序調(diào)用處理函數(shù),返回的值就是最終的 query。
4 總結(jié)通過源碼分析,可以看到制作一個(gè)這樣的庫有三個(gè)步驟:
創(chuàng)建 context 存儲(chǔ)結(jié)構(gòu)化 query 信息。
創(chuàng)建 builder 供用戶鏈?zhǔn)綍鴮懘a同時(shí)填充 context。
通過若干個(gè) SQL 子處理函數(shù)加上幾個(gè)主 statement 函數(shù)將其串聯(lián)起來生成最終 query。
最后在設(shè)計(jì)時(shí)考慮到 SQL 方言的話,可以將模塊拆成 核心、SQL、若干個(gè)方言庫,方言庫基于核心庫做拓展即可。
5 更多討論討論地址是:精讀《sqorn 源碼》 · Issue #103 · dt-fe/weekly
如果你想?yún)⑴c討論,請點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97831.html
摘要:精讀原文介紹了學(xué)習(xí)源碼的兩個(gè)技巧,并利用實(shí)例說明了源碼學(xué)習(xí)過程中可以學(xué)到許多周邊知識(shí),都讓我們受益匪淺。討論地址是精讀源碼學(xué)習(xí)如果你想?yún)⑴c討論,請點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...
摘要:會(huì)自動(dòng)觸發(fā)函數(shù)內(nèi)回調(diào)函數(shù)的執(zhí)行。因此利用并將依賴置為使代碼在所有渲染周期內(nèi),只在初始化執(zhí)行一次。同時(shí)代碼里還對等公共方法進(jìn)行了包裝,讓這些回調(diào)函數(shù)中自帶效果。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 react-easy-state 是個(gè)比較有趣的庫,利用 Proxy 創(chuàng)建了一個(gè)非常易用的全局?jǐn)?shù)據(jù)流管理方式。 import React from react; import { stor...
摘要:引言本周精讀的源碼是這個(gè)庫。這個(gè)庫的目的是為了實(shí)現(xiàn)的依賴注入。精讀那么開始源碼的解析,首先是整體思路的分析。討論地址是精讀源碼如果你想?yún)⑴c討論,請點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 本周精讀的源碼是 inject-instance 這個(gè)庫。 這個(gè)庫的目的是為了實(shí)現(xiàn) Class 的依賴注入。 比如我們通過 inject 描述一個(gè)成員變量,...
摘要:引言是一個(gè)版語法解析器生成器,具有分詞語法樹解析的能力。實(shí)現(xiàn)函數(shù)用鏈表設(shè)計(jì)函數(shù)是最佳的選擇,我們要模擬調(diào)用棧了。但光標(biāo)所在的位置是期望輸入點(diǎn),這個(gè)輸入點(diǎn)也應(yīng)該參與語法樹的生成,而錯(cuò)誤提示不包含光標(biāo),所以我們要執(zhí)行兩次。 1. 引言 syntax-parser 是一個(gè) JS 版語法解析器生成器,具有分詞、語法樹解析的能力。 通過兩個(gè)例子介紹它的功能。 第一個(gè)例子是創(chuàng)建一個(gè)詞法解析器 my...
摘要:精讀源碼一共行,我們分析一下其精妙的方式。更多討論討論地址是精讀新用法如果你想?yún)⑴c討論,請點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀幫你篩選靠譜的內(nèi)容。 1 引言 很高興這一期的話題是由 epitath 的作者 grsabreu 提供的。 前端發(fā)展了 20 多年,隨著發(fā)展中國家越來越多的互聯(lián)網(wǎng)從業(yè)者涌入,現(xiàn)在前端知識(shí)玲瑯滿足,概念、庫也越來越多。雖然內(nèi)容越來越多,但作為個(gè)體的...
閱讀 2465·2021-09-09 09:33
閱讀 2865·2019-08-30 15:56
閱讀 3119·2019-08-30 14:21
閱讀 891·2019-08-30 13:01
閱讀 855·2019-08-26 18:27
閱讀 3584·2019-08-26 13:47
閱讀 3449·2019-08-26 10:26
閱讀 1583·2019-08-23 18:38