Deno.inc が、Supabase とパートナーシップを締結。 Supabase Functions という Deno Deploy ベースのエッジワーカーが発表されていました。
Supabase は、データベースが提供されるんだけど MYSQL ではなく、PostgreSQL だそうな。 PostgreSQL は、利用経験が無いので一旦こちらを試すことにした。 (PostgreSQL 記事書きながら 8 年位前に使ったことあったのを思い出した。)
参考
環境構築 いつものように docker compose で環境の用意をします。 普段は、denoland/deno:centos を使っていました。 最近は 設定をしないと yum が動かなかったりと都合悪いことが増えてきました。 というわけで今回は、denoland/deno(debian) を使用します。
dockerfile 1 2 3 4 FROM denoland/deno:1.20 .4 RUN mkdir /usr/src/app WORKDIR /usr/src/app
docker-compose.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 version: "3" services: app: build: context: . dockerfile: Dockerfile privileged: true entrypoint: - /bin/sh volumes: - .:/usr/src/app:cached tty: true postgres: image: postgres:latest restart: always environment: POSTGRES_USER: ${POSTGRES_USER:-user} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} PGPASSWORD: ${PGPASSWORD:-pgpassword} POSTGRES_DB: ${APP_DB:-app} TZ: "Asia/Tokyo" ports: - 5432 :5432 volumes: - ./postgres:/var/lib/postgresql/data pgadmin: image: dpage/pgadmin4 restart: always ports: - 8080 :80 environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-test@test.com} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-password} volumes: - ./pgadmin:/var/lib/pgadmin depends_on: - postgres
.env 1 2 3 4 5 6 POSTGRES_USER=hogehoge POSTGRES_PASSWORD=hogehoge PGPASSWORD=hogehoge POSTGRES_DB=hogehoge PGADMIN_DEFAULT_EMAIL=hogehoge PGADMIN_DEFAULT_PASSWORD=hogehoge
以上を用意して以下を操作。
1 2 3 4 5 docker-compose up -d docker-compose exec app bash deno -V deno 1.20.4
pgadmin 接続 普段は SQL クライアントとして HeidiSQL を使っているのですが、こちらから PostgreSQL への接続はできませんでした。 そのため、クライアントとして pgadmin を用意してあります。 localhost:8080 にアクセスすると、pgadmin へログインできます。
「新しいサーバーかを追加」から設定を入れ、pgadmin -> postgres の接続設定を投入します。
ざっとデータを見るのは楽でした。
実装 今回は、PostgreSQL のクライアントとして deno.land/x の検索結果から 星の比較的多いもの 2 つ試します。
postgresjs README に TypeScript 対応の記載はあるものの、上手いこといかないので JavaScript で記述。
sample1.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 import "https://deno.land/std@0.133.0/dotenv/load.ts" ;import postgres from "https://deno.land/x/postgresjs/mod.js" ;const sql = postgres ({ host : "postgres" , port : 5432 , database : Deno .env .get ("POSTGRES_DB" ), username : Deno .env .get ("POSTGRES_USER" ), password : Deno .env .get ("POSTGRES_PASSWORD" ), }); async function createUserTable ( ) { await sql` CREATE TABLE IF NOT EXISTS users ( id serial primary key, name text, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); ` ;} async function createUserFunction ( ) { await sql`DROP FUNCTION IF EXISTS set_update_time() CASCADE;` ; await sql` CREATE FUNCTION set_update_time() RETURNS trigger AS ' begin new.updated_at := ''now''; return new; end; ' LANGUAGE plpgsql;` ; await sql`CREATE TRIGGER update_trigger BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE set_update_time();` ; } async function getUsers ( ) { return await sql` select id, name, created_at, updated_at from users order by id ` ;} async function insertUser (name ) { return await sql` insert into users (name) values (${name} ) returning name ` ;} async function updateUser (id, { name } ) { return await sql` update users set name = ${name} where id = ${id} ` ;} await createUserTable ();await createUserFunction ();await insertUser ("AAA1" );await insertUser ("AAA2" );await updateUser (1 , { name : "BBB1" });console .log (await getUsers ());await sql.end ();
実行すると以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 deno run --allow-net --allow-env --allow-read --unstable sample1.js [ { id : 1, name: "BBB1" , created_at: 2022-04-03T18:02:29.743Z, updated_at: 2022-04-03T18:02:29.749Z }, { id : 2, name: "AAA2" , created_at: 2022-04-03T18:02:29.747Z, updated_at: 2022-04-03T18:02:29.747Z } ]
実行には、–unstable が必要。
postgres.js の提供している 関数見てみると気持ち悪い点があります。関数に()が無い。 タグ付きテンプレート関数というものだそうです。これがエスケープもそうです。 というわけでエスケープは気にせず ${} を使って文字列を埋め込んでよい。
とりあえず、データの取得ができました。
deno-postgres 次は、deno 向け実装の deno-postgres を試します。
sample2.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import "https://deno.land/std@0.133.0/dotenv/load.ts" ;import { Client } from "https://deno.land/x/postgres/mod.ts" ;const client = new Client ({ hostname : "postgres" , port : 5432 , database : Deno .env .get ("POSTGRES_DB" ), user : Deno .env .get ("POSTGRES_USER" ), password : Deno .env .get ("POSTGRES_PASSWORD" ), }); await client.connect ();interface User { id : number; name : string; created_at : Date ; updated_at : Date ; } async function createUserTable ( ) { await client.queryObject ({ text : ` CREATE TABLE IF NOT EXISTS users ( id serial primary key, name text, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); ` , }); } async function createUserFunction ( ) { await client.queryObject ({ text : ` DROP FUNCTION IF EXISTS set_update_time() CASCADE; CREATE FUNCTION set_update_time() RETURNS trigger AS ' begin new.updated_at := ''now''; return new; end; ' LANGUAGE plpgsql; CREATE TRIGGER update_trigger BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE set_update_time(); ` , }); } async function getUsers ( ): Promise <any[][]> { const users = await client.queryArray ({ text : ` select id, name, created_at, updated_at from users order by id ` , }); return users.rows ; } async function getUsersObject ( ): Promise <User []> { const users = await client.queryObject < User > ({ text : ` select id, name, created_at, updated_at from users order by id ` , fields : ["id" , "name" , "created_at" , "updated_at" ], }); return users.rows ; } async function insertUser ({ name }: { name: string } ) { const users = await client.queryObject ({ text : ` insert into users (name) values ($1) ` , args : [name], fields : ["name" ], }); return users; } async function updateUser (id: number, { name }: { name: string } ) { const users = await client.queryObject ({ text : ` update users set name = $1 where id = $2 ` , args : [name, id], fields : ["name" ], }); return users; } await createUserTable ();await createUserFunction ();await insertUser ({ name : "AAA1" });await insertUser ({ name : "AAA2" });await updateUser (1 , { name : "BBB1" });console .log (await getUsers ());console .log (await getUsersObject ());await client.end ();
実行すると以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 deno run --allow-net --allow-env --allow-read sample2.ts [ [ 1, "BBB1" , 2022-04-03T18:31:16.654Z, 2022-04-03T18:31:16.662Z ], [ 2, "AAA2" , 2022-04-03T18:31:16.660Z, 2022-04-03T18:31:16.660Z ] ] [ { id : 1, name: "BBB1" , created_at: 2022-04-03T18:31:16.654Z, updated_at: 2022-04-03T18:31:16.662Z }, { id : 2, name: "AAA2" , created_at: 2022-04-03T18:31:16.660Z, updated_at: 2022-04-03T18:31:16.660Z } ]
面白いのは、select 結果を 2 種の方法で取り出せること。 ナイスポイントは、クエリの取得結果に型が付くこと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function getUsersObject ( ): Promise <User []> { const users = await client.queryObject <User >({ text : ` select id, name, created_at, updated_at from users order by id ` , fields : ["id" , "name" , "created_at" , "updated_at" ], }); return users.rows ; }
例えば、getUsersObject()から型定義を外した以下のものを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function getUsersObject ( ) { const users = await client.queryObject ({ text : ` select id, name, created_at, updated_at from users order by id ` , fields : ["id" , "name" , "created_at" , "updated_at" ], }); return users.rows ; }
そして取得結果を扱う以下のコードを用意します。
1 2 3 4 5 const users = await getUsersObject ();const ids = users.map ((u ) => { return u.id * u.id ; });
実行すると型チェックでエラーとなります。
1 2 3 4 5 6 7 8 9 error: TS2571 [ERROR]: Object is of type 'unknown'. return u.id * u.id; ^ at file:///usr/src/app/deno-postgres.ts:134:10 TS2571 [ERROR]: Object is of type 'unknown'. return u.id * u.id; ^ at file:///usr/src/app/deno-postgres.ts:134:17
今回 2 つ PostgreSQL のクライアントを試してみました。 使っていく場合には、deno-postgres の方を選択したいかなと感じました。
ではでは。