點燈坊

失くすものさえない今が強くなるチャンスよ

以 Function Pipeline 使用 Knex

Sam Xiao's Avatar 2021-10-22

Knex 是很優秀的 SQL Query Builder,唯 Knex 是以 Method Chaining 設計,是否有機會以 Function Pipeline 使用 Knex 呢 ?

Version

Knex 0.20.13

SQL

SELECT count(*)
FROM books

實務上常使用 count(*) 得知筆數。

pipeline000

Knex

import knex from 'knex'
import { log, tug } from 'wink-fp'

mySQL ('books')
  .count ({ count: '*'})
  .then (tug ('count'))
  .then (log)

Knex 會使用 count,再搭配 Wink-fp 的 tug 取出結果。

我們可發現 Knex 的 count 回傳 promise,而 tug 為 synchronous function,因此需要用 then 隔開

Functional

import knex from 'knex'
import { pipeP, tug, log } from 'wink-fp'

let count = (...v) => k => k.count (...v)

pipeP (
  count ({ count:'*' }),
  tug ('count'),
  log
) (mySQL ('books'))

若將 count 包成 function,就可使用 pipeP 將 Knex 的 count 與 synchronous 的 tug 同時包在一起,不必再使用 then 隔開。

pipeline001

SQL

SELECT title, price
FROM books

SELECT 也為 SQL 基本應用。

pipeline002

Knex

import knex from 'knex'
import { map } from 'ramda'
import { format, log } from 'wink-fp'

mySQL ('books')
  .select ('title', 'price')
  .then (map (format ('{title}: {price}')))
  .then (log)

Knex 會使用 select,再搭配 Ramda 的 map 與 Wink-fp 的 format 整理出想要的格式。

我們可發現 Knex 的 select 回傳 promise,而 map 為 synchronous function,因此需要用 then 隔開

Functional

import knex from 'knex'
import { map } from 'ramda'
import { format, log, pipeP } from 'wink-fp'

let select = (...v) => k => k.select(...v)

pipeP (
  select ('title', 'price'),
  map (format ('{title}: {price}')),
  log
) (mySQL ('books')

若將 select 包成 function,就可使用 pipeP 將 Knex 的 select 與 synchronous 的 map 同時包在一起,不必再使用 then 隔開。

pipeline003

SQL

SELECT title, price
FROM books
WHERE price = 100

SELECT + WHERE 為 SQL 最常見應用。

pipeline004

Knex

import knex from 'knex'
import { map } from 'ramda'
import { format, pipeP, log } from 'wink-fp'

mySQL ('books')
  .select ('title', 'price')
  .where ({ price: 100 })
  .then (map (format ('{title}: {price}')))
  .then (log)

Knex 會使用 selectwhere,再搭配 Ramda 的 map 與 Wink-fp 的 format 整理出想要的格式。

Functional

import knex from 'knex'
import { map, pipe } from 'ramda'
import { format, log, pipeP } from 'wink-fp'

let select = (...v) => k => k.select (...v)
let where = (...v) => k => k.where (...v)

pipeP (pipe (
  select ('title', 'price'),
  where ({ price: 100 })),
  map (format ('{title}: {price}')),
  log
) (mySQL ('books'))

第 5 行

let select = (...v) => k => k.select (...v)
let where = (...v) => k => k.where (...v)

先將 selectwhere 包成 function。

第 8 行

pipeP (pipe (
  select ('title', 'price'),
  where ({ price: 100 })),
  map (format ('{title}: {price}')),
  log
) (mySQL ('books'))

若使用兩個以上的 Knex function,則要先使用 pipe 先組合,再搭配 pipeP 組合其他 synchronous function。

pipeK

import knex from 'knex'
import { map, pipe } from 'ramda'
import { format, log, pipeP } from 'wink-fp'

let select = (...v) => k => k.select (...v)
let where = (...v) => k => k.where (...v)
let pipeK = (...ks) => (...fs) => pipeP (pipe (...ks), ...fs)

pipeK (
  select ('title', 'price'),
  where ({ price: 100 })
)(
  map (format ('{title}: {price}')),
  log
) (mySQL ('books'))

當 Knex function 與 synchronous function 混用時,會出現 pipepipeP 同時混用的窘境,是否有優化空間呢 ?

第 7 行

let pipeK = (...ks) => (...fs) => pipeP (pipe (...ks), ...fs)

pipePpipe 組合成 pipeK,其中 K 表示 K nex,表專門適用於 Knex 的 function pipeline 使用。

Argument 分兩族群,第一個 argument 組合 Knex function,第二個 argument 組合 synchronous function。

第 9 行

pipeK (
  select ('title', 'price'),
  where ({ price: 100 })
)(
  map (format ('{title}: {price}')),
  log
) (mySQL ('books'))

如此只要使用單獨 pipeK 即可,上面是 Knex function,而下面為 synchronous function。

pipeline005

Conclusion

  • 若只使用單一 Knex function,可直接使用 pipeP 組合 Knex function 與其他 function
  • 若要同時使用多個 Knex function,可使用 pipe 將 Knex function 組合,再使用 pipeP 組合其他 synchronous function,亦可直接使用專屬的 pipeK 一次解決
  • 本文只是牛刀小試將 Knex 的 countselectwhere 抽成 function,並搭配 pipeK 同時組合 Knex function 與 synchronous function,實務上可將這些 function 整理成 library 供日後使用