MySQLのJSONからboolean型のGenerated Columnを定義する時の注意点

  • このエントリーをはてなブックマークに追加

確認したバージョンは、MySQL8.0.25。
JSONのtrue/falseを参照してMySQLのboolean(tinyint)にする時、ついうっかりvarchar等と同じ感覚でjson_unquoteをするとエラーになる。

テスト用の定義

create table test1 (
id bigint not null,
data json not null,
name varchar(32) as (json_unquote(data->'$.name')) stored,
flag1 boolean as (json_unquote(data->'$.flag1')) stored, -- ※間違い
flag2 boolean as ((data->'$.flag2')) stored,
constraint pk_test1 primary key(id)
);

間違い

insert into test1 set id = 1, data = '{"name":"hello", "flag1": true, "flag2": true}';

flag1列でエラー。boolean列にjson_unquoteしているから。

ERROR 1366 (HY000): Incorrect integer value: 'true' for column 'flag1' at row 1

正しい

flag2はjson_unquoteをしていないので成功する。

insert into test1 set id = 1, data = '{"name":"hello", "flag2": true}';
insert into test1 set id = 2, data = '{"name":"hello", "flag2": false}';
select * from test1;
+----+-----------------------------------+-------+-------+-------+
| id | data | name | flag1 | flag2 |
+----+-----------------------------------+-------+-------+-------+
| 1 | {"name": "hello", "flag2": true} | hello | NULL | 1 |
| 2 | {"name": "hello", "flag2": false} | hello | NULL | 0 |
+----+-----------------------------------+-------+-------+-------+

型は何になっている??

MySQLにはpg_typeofみたいなものはないので、Selectの結果でTEMPテーブルを作って確認します。

create temporary table tmp1
select
(json_extract('{"name":"hello", "flag1": true}', '$.flag1')) as c1,
(json_unquote(json_extract('{"name":"hello", "flag1": true}', '$.flag1'))) as c2
;

desc tmp1;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| c1 | json | YES | | NULL | NULL |
| c2 | longtext | YES | | NULL | NULL |
+-------+----------+------+-----+---------+-------+

select * from tmp1;
+------+------+
| c1 | c2 |
+------+------+
| true | true |
+------+------+

json_unquoteしたものはlongtextのようです。
つまり、jsonからtinyintへの変換は成功するが、longtextからtinyintへの変換は失敗するということになります。

確認してみます。

select cast(json_extract('{"name":"hello", "flag1": true}', '$.flag1') as signed) as x;
+------+
| x |
+------+
| 1 |
+------+
1 row in set (0.00 sec)

対して、

select cast(json_unquote(json_extract('{"name":"hello", "flag1": false}', '$.flag1')) as signed) as x;
+------+
| x |
+------+
| 0 |
+------+
1 row in set, 1 warning (0.00 sec)

駄目な上に警告がでています。

 show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1292 | Truncated incorrect INTEGER value: 'false' |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)

検証の単純化

-- OK
select cast(true as signed);
select cast(cast('true' as json) as signed);

-- NG
select cast('true' as signed)

そんなわけで、JSONのbooleanを扱うときは、気をつけましょうということで。

MySQL8でOut of sort memoryが発生するようになった

  • このエントリーをはてなブックマークに追加

MySQL8.0.21から8.0.25にアップグレードした後、次のような単純なクエリを発行したらERROR 1038 (HY001): Out of sort memory, consider increasing server sort buffer sizeが発生するようになった。

SELECT *
FROM hoge_table
ORDER BY created_at DESC LIMIT 10

このテーブルは次のように、JSONカラムとそれを参照する大量のGeneratedカラムを持つテーブルです。

create table hoge_table(
id varchar(32) ascii not null,
data json not null,
c1 varchar(256) as (json_unquote(data->'$.c1')) stored,
c2 varchar(256) as (json_unquote(data->'$.c2')) stored,
c3 varchar(256) as (json_unquote(data->'$.c3')) stored,
.... 20カラムくらい,
constraint pk_hoge_table primary key (id)
) default charset utf8mb4 row_format=compressed key_block_size=4;

Bug Ticket:103225に情報があります。

対策

sort_buffer_sizeが256KBなので1Mに増やしました。innodb_sort_buffer_sizeではなくsort_buffer_sizeです。私は幸いにして、これで回避できました。

オンラインで変更

set global sort_buffer_size = 1*1024*1024;
# 変わらない場合は..
set @@sort_buffer_size = 1*1024*1024;

show variables like '%sort%';
/etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
sort_buffer_size = 1M

参考

RubyのDatadog ClientからMetricsが記録されない場合がある

  • このエントリーをはてなブックマークに追加

dogstatsd-rubyを利用しています。
バージョン5から、Client側が内部バッファを持つようになった影響で、終了時には明示的にflushしないとバッファの内容が残ったままプログラムが終了し、Metricsが喪失します。

auto-closeの処理が入っていると思いきや、一瞬だけ入ったのですが、すぐにrevertされました。

This feature is introducing way too much complexity for applications using forks: since the auto-close feature use a global list to store the instances list, it could lead to a fork dying trying to close an instance which has been created before the fork.
It’s not wrong, but it’s complexifying the thinking process. It is better and important to manually close instances when they are not needed anymore (they create a socket and a thread, not closing the instance would leak these resources).

プログラムをForkする場合に、すごく複雑になるから。だそうです。

そんなわけで、自前で簡単に処理するように。

require "datadog/statsd"
datadog = Datadog::Statsd.new()
at_exit {
datadog.flush(sync: true) if datadog
}

v4と同じ挙動である、クライアントでバッファリングしないようにする事も可能です。その場合は明示的にflushする必要はありません。(大量にMetricsを送る場合は非推奨)

require "datadog/statsd"
datadog = Datadog::Statsd.new(single_thread: true, buffer_max_pool_size: 1)

参考

HTMLタグで属性が重複したときの挙動について

  • このエントリーをはてなブックマークに追加

次のタグのように属性が重複している場合、パーサーはどのように処理されるのだろうか。
もともと、Nokogiriを使ってパースしていて属性が取れないぞとなって調べたのでメモ。

<div id="s" class="a" class="b"></div>

結論から言うと、先に出現した属性が優先され、後続の重複した属性は無視されます。
Firefox, Chrome, Gumbo-parser, Nokogiri(sax-parser)などで確認しました。

仕様

WHATWGの仕様はこちらにあります。

There must never be two or more attributes on the same start tag whose names are an ASCII case-insensitive match for each other.

https://html.spec.whatwg.org/multipage/syntax.html

仕様的に、2回以上含めることはパースエラーとなる。

This error occurs if the parser encounters an attribute in a tag that already has an attribute with the same name. The parser ignores all such duplicate occurrences of the attribute.

https://html.spec.whatwg.org/#parse-error-duplicate-attribute

しかし、現実的にパースエラーにすると色々と困るので、エラーとして停止する/しないを選択することができ、殆どのブラウザはHTML(not XMLを解釈する時には、重複属性の時は最初の値を採用し、後続に出現した重複属性は無視をする、という選択をしているようです。

参考

Vue3でAタグをクリックした時に遷移させないで自前の処理をする

  • このエントリーをはてなブックマークに追加

Vue3にて、例えば、AタグをButton代わりに使う場合、こんな感じで書きたいわけですが、HashベースのRouterを使っている場合、#で遷移が発生してしまいます。

<a href="#" @click="linkClick" class="btn btn-outline-primary">link</a>

なのでこう書きます。

<a href="#" @click.prevent.stop="linkClick" class="btn btn-outline-primary">link</a>

stopでclickイベントの伝播が止まり、preventで既定のアクションを処理しない。
例えばformのsubmitをさせない場合はpreventを指定する(<form @submit.prevent="mySubmit">)

参考

Vue3でBootstrapとBootstrap Iconを利用する

  • このエントリーをはてなブックマークに追加
npm install --save bootstrap bootstrap-icons

bootstrapをcustomする

main.js
import 'bootstrap'
import '@/scss/custom.scss'

bootstrapの変数を上書きしてからbootstrapのscssをimportすると反映される。

scss/custom.scss
$body-bg: #000;
@import "node_modules/bootstrap/scss/bootstrap";

bootstrap iconsを利用する

main.js
import 'bootstrap-icons/font/bootstrap-icons.css'

iconsのcssをimportしてから、

App.vue
<i class="bi bi-file-bar-graph"></i>

参考

Ruby標準のLoggerでスレッドIDを出力する

  • このエントリーをはてなブックマークに追加

その1 / procを指定する

formatterにprocを直接指定する。
色々と制限があります。あまりオススメしません。


def main(logger)
logger.info("main"){ "こんにちは" }
begin
raise "エラー"
rescue => err
logger.error("main"){ err }
end
end

custom = Logger.new(STDOUT, formatter: proc { |severity, time, progname, msg|
# msgが例外の場合、originalは表示されるけど、これはされない
"%s, [%s#%d/#%d] %5s -- %s: %s\n" % [severity[0..0], time.strftime("%Y-%m-%dT%H:%M:%S.%6N "), Process.pid, Thread.current.object_id, severity, progname, msg]
})
main(custom)
I, [2021-07-11T10:36:43.688551 #4163]  INFO -- main: こんにちは
E, [2021-07-11T10:36:43.688635 #4163] ERROR -- main: エラー (RuntimeError)
app.rb:7:in `main'
app.rb:14:in `<main>'

その2 / Logger::Formatterを継承する

Logger::Formatterを継承して自分用のFormatterを作り、Loggerのfometterに指定します。
この方法は直接Procを指定するわけではないので、標準クラスが持っているメソッドであるformat_datetimemsg2strが使えます。


def main(logger)
logger.info("main"){ "こんにちは" }
begin
raise "エラー"
rescue => err
logger.error("main"){ err }
end
end

class CustomFormatter < Logger::Formatter
CustomFormat = "%s, [%s#%d/#%d] %5s -- %s: %s\n".freeze
def initialize
super
end
def call(severity, time, progname, msg)
CustomFormat % [severity[0..0], format_datetime(time), Process.pid, Thread.current.object_id, severity, progname, msg2str(msg)]
end
end
custom2 = Logger.new(STDOUT, formatter: CustomFormatter.new)
main(custom2)

I, [2021-07-11T10:36:43.688747 #4163/#70368626219440]  INFO -- main: こんにちは
E, [2021-07-11T10:36:43.688773 #4163/#70368626219440] ERROR -- main: エラー (RuntimeError)
app.rb:7:in `main'
app.rb:32:in `<main>'

参考

vueでscssを利用する

  • このエントリーをはてなブックマークに追加

vue-cliのバージョンは4.5.13を想定。
vue createした時に「Manually select features」を選択して「CSS Pre-processors」を選択すれば最初から入るが、「Default」を選択した場合は、babeleslintが有効になるだけである。

vue --version
@vue/cli 4.5.13

後から入れる場合は、sasssass-loaderをdevDependenciesに追加する。ただし、sass-loaderは現時点でバージョンが12.1.0であり、webpackの依存バージョンが競合してエラーになってしまう。
回避するためにはバージョンを指定してインストールする。それぞれvue-cliで最初に有効にした場合にインストールされるバージョンを指定する。
これらのバージョンは、ここで参照する事ができます。

npm install --save-dev sass@1.26.5
npm install --save-dev sass-loader@8.0.2

上記では8.0.2を指定していますが、vue-cliが4.5.13で作ったpackage.jsonに対しては、sass-loader@10.2.0まではインストール可能です。

sass-loaderのpeerDependenciesが、10.2.0では次の指定に対して、

10.2.0
"webpack": "^4.36.0 || ^5.0.0"

11.0.0からは、次のようになっているからです。

11.0.0
"webpack": "^5.0.0"

@vue/cli-plugin-babelのバージョンは4.5.13で、そのdependenciesに"webpack": "^4.0.0"とあり、4.46.0が実際にインストールされています。

書き方

vueファイルのstyleにlang="scss"と書く。

サンプル
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
body {
color: blue;
p {
font-weight: bold;
}
}
</style>

参考

Vue3で環境変数の設定をする

  • このエントリーをはてなブックマークに追加

Vue2とVue3では設定方法が異なります。この記事ではVue3に関する設定方法を。
npm run serve mode=mockとして起動することで、.env.mockのファイルを読み込みます。開発環境の時はNODE_ENVdevelopmentなので、.env.developmentが読み込まれます。

環境変数名はVUE_APP_で始まる必要があり、それ以外は無視されてしまう。

.env.development
VUE_APP_HOGEFUGA=true

process.env.VUE_APP_HOGEFUGAで参照可能。

.envは全てのケースにおいて読み込まれますが、優先順位があり、.env.developmentが先に読み込まれる。また、.env.local.env.development.localのように末尾に.localのファイルがあれば、それが優先的に読み込まれる。.localは各個人の環境でのみ利用することを想定しているファイルで.gitignoreの対象となっている。

例えば、--mode=developmentで起動した場合、.env.development.local -> .env.development -> .env.local -> .env の順番に読み込みされて、同じ環境変数名がある場合は先勝ちである。

これらの処理はdotenvdotenv-expandのモジュールがやってるのだと思うのですが、そんなコードは見つけられず、dotenv-flowにこのような処理があるけど、vue3からはリンクはされておらず。

どうやら自前で処理している様子。
vue-cliのcli-serviceのService.jsに処理がありました。
dotenvを利用しており、環境変数DEBUG=trueをつけて実行すると、dotenvのデバッグログが有効になって、そのあたりのログが出力されます。

参考

Vue3でWebページを表示しようとしたらInvalid Host Headerと表示される

  • このエントリーをはてなブックマークに追加

例えば、 example.local127.0.0.1にマッピングして、example.localでアクセスした時にInvalid Host Headerと表示される。

vue.config.jsで次の設定を入れる

module.exports = {
// options...
devServer: {
disableHostCheck: true
}
}

参考