knex の困ったところハマったところなど
◆ 慣れてくると結構使いやすい
以前は sql`SELECT ${variable}` みたいにテンプレートリテラルで書けるものを自作して使ってましたが 世間で使われてるライブラリのほうがいいかなとか思って最近は Node.js の DB アクセス系ツールで一番人気らしい knex を使ってました
基本的なことをするには簡単に使えて便利ですが困ったところもありました
Promise 的に考えて then ですること無いなら書かなくていいやと思って書かないと実行されません
だと value が 2 のほうしか insert は行われません
await するか async 関数で return すれば内部的に then メソッドが呼び出されるので 何もしない then を書きたくないならこっちもありです
async 関数じゃない普通の関数だと return しても then は呼び出されません
ただし insert に空配列を渡した場合はクエリが空っぽになるので何も実行されず 結果も knex の特別なオブジェクトです
insert select を使えば insert クエリを実行してかつ結果が 0 件になりますが 書き方が特別になるのでそういうことをするくらいなら事前に空配列ならその場で 空配列を return するみたいな関数を通したほうが簡単です
knex は PostgreSQL 以外も対応してるから PostgreSQL 独自の NOTIFY/LISTEN 機能には対応しないようです
なら raw で送信すればいいかと思ってやってみました
問題なく SQL が作れています
送信してみると
構文エラーになりました
NOTIFY の payload に 「?」 は対応していないようです
これは knex というより pg ドライバや PostgreSQL 自体が対応していないからみたいです
SELECT 句で pg_notify 関数を使えばできるみたいです
https://github.com/brianc/node-postgres/issues/1258
toQuery で得られる SQL は正しいものなのでそれを raw で送ることもできます
https://github.com/knex/knex/issues/2441
原因は未設定の knex を使ったことでした
こういう感じで 設定済みが pg で require したものが knex という変数に入っていて pg を使わないといけないのにコピペのせいで knex が混ざってました
こういうことを防ぐために ドキュメントなどでいったん knex という名前で require したものを保持していないのかもです
ただ config の取得が非同期になったりで pg を作るのが後回しになることもあったり 設定が違うインスタンスを別に作りたいケースもあったりなので 気をつけるしか無いですね
使ってない sqlite3 周りのエラーメッセージなら未設定の knex を使ってるかもとおぼえておけばそこまで困らず対処できます
これまでは
みたいな指定をしていて オブジェクトの配列を結果として取得できていました
必要な返り値が id だけだったので
と書いたところ 返り値のフォーマットが異なっていて困りました
id の数値の配列になっていました
配列で取得するには要素が 1 つでも配列として渡さないとダメなようです
例外的に 「*」 は全部の列を表すので文字列でも オブジェクトの配列で取得できていました
基本的なことをするには簡単に使えて便利ですが困ったところもありました
then で実行
以前 Short の方でも書いた内容ですが 実行メソッドが then なんですPromise 的に考えて then ですること無いなら書かなくていいやと思って書かないと実行されません
knex("table").insert({value: 1})
knex("table").insert({value: 2}).then(result => {})
だと value が 2 のほうしか insert は行われません
await するか async 関数で return すれば内部的に then メソッドが呼び出されるので 何もしない then を書きたくないならこっちもありです
async function knexInsert1() {
await knex("table").insert({value: 3})
}
async function knexInsert2() {
return knex("table").insert({value: 4})
}
async 関数じゃない普通の関数だと return しても then は呼び出されません
insert で空配列の場合
こっちも Short に書いた内容ですが knex では更新がない場合には結果が空配列になりますただし insert に空配列を渡した場合はクエリが空っぽになるので何も実行されず 結果も knex の特別なオブジェクトです
insert select を使えば insert クエリを実行してかつ結果が 0 件になりますが 書き方が特別になるのでそういうことをするくらいなら事前に空配列ならその場で 空配列を return するみたいな関数を通したほうが簡単です
const insertRows = async values => knex("table").insert(values).returning("*")
// ⇩
const insertRows = async values => (!values || values.length === 0) ? [] : knex("table").insert(values).returning("*")
NOTIFY/LISTEN 非対応
PostgreSQL の NOTIFY/LISTEN 機能を使おうとしたら knex は非対応でしたknex は PostgreSQL 以外も対応してるから PostgreSQL 独自の NOTIFY/LISTEN 機能には対応しないようです
NOTIFY の ? に非対応
上の通り NOTIFY/LISTEN は非対応で notify などのメソッドはありませんなら raw で送信すればいいかと思ってやってみました
> knex = require("knex")({ client: "pg", connection: { database: "test1", user: "postgres" } })
> knex.raw("NOTIFY ch, ?", [JSON.stringify({x: 1})]).toQuery()
`NOTIFY ch, '{"x":1}'`
問題なく SQL が作れています
送信してみると
> knex.raw("NOTIFY ch, ?", [JSON.stringify({x: 1})]).then(console.log)
Unhandled rejection error: "$1"またはその近辺で構文エラー
構文エラーになりました
NOTIFY の payload に 「?」 は対応していないようです
これは knex というより pg ドライバや PostgreSQL 自体が対応していないからみたいです
SELECT 句で pg_notify 関数を使えばできるみたいです
https://github.com/brianc/node-postgres/issues/1258
toQuery で得られる SQL は正しいものなのでそれを raw で送ることもできます
> const sql = knex.raw("NOTIFY ch, ?", [JSON.stringify({x: 1})]).toQuery()
> knex.raw(sql).then(console.log)
Result { ... }
なぜか sqlite3 のエラー
PostgreSQL しか使ってないのになぜかエラーメッセージに sqlite3 が出て困りましたhttps://github.com/knex/knex/issues/2441
原因は未設定の knex を使ったことでした
const knex = require("knex")
const config = require("./config")("pgsql")
const pg = knex({ client: "pg", connection: config })
pg("foo")
.whereIn(
"id",
knex("bar").where("num", ">", 100)
)
).select("*")
こういう感じで 設定済みが pg で require したものが knex という変数に入っていて pg を使わないといけないのにコピペのせいで knex が混ざってました
こういうことを防ぐために ドキュメントなどでいったん knex という名前で require したものを保持していないのかもです
ただ config の取得が非同期になったりで pg を作るのが後回しになることもあったり 設定が違うインスタンスを別に作りたいケースもあったりなので 気をつけるしか無いですね
使ってない sqlite3 周りのエラーメッセージなら未設定の knex を使ってるかもとおぼえておけばそこまで困らず対処できます
returning の結果
returning を使うと insert などで更新された行のデータを取得できますこれまでは
knex("table").insert(values).returning("*")
knex("table").insert(values).returning(["id", "name", "ts", "user_id"])
みたいな指定をしていて オブジェクトの配列を結果として取得できていました
必要な返り値が id だけだったので
knex("table").insert(values).returning("id")
と書いたところ 返り値のフォーマットが異なっていて困りました
id の数値の配列になっていました
配列で取得するには要素が 1 つでも配列として渡さないとダメなようです
例外的に 「*」 は全部の列を表すので文字列でも オブジェクトの配列で取得できていました