MongoDB(2) - データベース操作編

前回はMongoDBを使えるようにセッティングを行いました。

今回はMongoDBのデータベースに関する操作を行ってみます。

データベース作成

データベースの作成を行います。データベース名はdb1とします。

[Mongoシェル]

1
2
> use db1
switched to db db1

useコマンドは、データベースの選択を行います。

ただしデータベースを選択しただけでデータベースが作成されるわけではありません。

データベースを選択した後にコレクションの作成などを行って、はじめてデータベースが作成されます。

データベースの一覧表示

データベースの一覧を表示します。

[Mongoシェル]

1
2
3
4
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB

admin、config、local という3つのデータベースが存在することを確認できます。

データベースの統計情報

データベースの統計情報を表示します。

各種性能を計測したり、運用時にデータ量を確認する場合に便利です。

(useコマンドで選択されているデータベースの情報が表示されます。)

[Mongoシェル]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> db.stats()
{
"db" : "db1",
"collections" : 0,
"views" : 0,
"objects" : 0,
"avgObjSize" : 0,
"dataSize" : 0,
"storageSize" : 0,
"totalSize" : 0,
"indexes" : 0,
"indexSize" : 0,
"scaleFactor" : 1,
"fileSize" : 0,
"fsUsedSize" : 0,
"fsTotalSize" : 0,
"ok" : 1
}

参照頻度の高い情報を下記の一覧にまとめます。

フィールド名説明
collectionsデータベースの中にあるコレクション数
dataSize圧縮される前のデータサイズ
storageSizeコレクションに割り当てられたスペースの合計サイズ
(データ圧縮されたあとのサイズ)
indexesインデックス数
indexSizeインデックスのデータ量の合計
fileSizeストレージに保存されるファイルのサイズ

データベースの削除

データベースを削除します。

[Mongoシェル]

1
2
> db.dropDatabase()
{ "ok" : 1 }

MongoDBでは簡単にデータベースを作成できるので、一時的なデータベースや不要になったデータベースは削除しておきましょう。

Pythonで操作

上記の操作をPythonでまとめて行うと、次のようになります。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pymongo import MongoClient

# MongoDB操作用のインスタンスを作成
client = MongoClient() # [IPとポートを指定する場合] MongoClient('10.200.243.203', 27017')

# データベースの取得(存在しない場合は作成)
db1 = client.db1

# データベースの一覧取得
lst_db = client.database_names()
print(lst_db)

# データベースの統計情報取得
db_info = db1.command("dbstats")
print(db_info)

# データベースの削除
client.drop_database('db1')

次回はコレクションに関する操作を行います。

MongoDB(1) - 準備編

今回からMongoDBに関する記事を書いていきます。

コンソールからの操作とその操作をPythonで行う場合の2種類でMongoDBの動作確認を行います。

(最近Python関連の記事を全く書いていないことに気づきました・・・😨😨)

MongoDBとは

MongoDBは、NoSQLの1つです。

NoSQLの中でもMongoDBはシェアがトップという調査結果があります。

MongoDBが選ばれる理由としては下記が挙げられます。

  • スキーマレスにより保守性が高い
  • 高パフォーマンス
  • RDBと似た操作により学習効率が高い
  • オープンソースのため無料
  • 簡単な設定でスケールアウト(負荷分散)が可能

MongoDBのインストール

下記サイトからMongoDBのインストーラをダウンロードし、MongoDBをインストールします。

各環境に合わせてインストールを行ってください。

MongoDB Community Server - https://www.mongodb.com/try/download/community

(Community Serverが無料版になります。)

コンソールの準備(Mongoシェル)

インストール先にbinフォルダ(ディレクトリ)があり、そこにパスを通しておくとコンソールからMongoシェルを実行することができます。

コンソールからmongoと入力すると、Mongoシェルが起動します。

[コンソール]

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
>mongo
MongoDB shell version v5.0.2
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("958c42c1-e0fe-45ec-b53e-d1a0db49c950") }
MongoDB server version: 5.0.2
================
Warning: the "mongo" shell has been superseded by "mongosh",
which delivers improved usability and compatibility.The "mongo" shell has been deprecated and will be removed in
an upcoming release.
We recommend you begin using "mongosh".
For installation instructions, see
https://docs.mongodb.com/mongodb-shell/install/
================
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
https://docs.mongodb.com/
Questions? Try the MongoDB Developer Community Forums
https://community.mongodb.com
---
The server generated these startup warnings when booting:
2021-08-14T09:24:46.540+09:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
---
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
>

上記のように表示されたら、成功です。

このMongoシェルから、MongoDBを操作することができます。

Python用の準備

PythonからMongoDBを操作するためにはpymongoライブラリを使用します。

pipコマンドでインストールします。

[コンソール]

1
2
3
4
5
6
>pip install pymongo
Collecting pymongo
Downloading pymongo-3.12.0-cp38-cp38-win_amd64.whl (397 kB)
|████████████████████████████████| 397 kB 6.8 MB/s
Installing collected packages: pymongo
Successfully installed pymongo-3.12.0

以上で、コンソールとPythonからMongoDBを利用する準備ができました。


次回から、MongoDBをいろいろと操作していきます。

Ethereum(52) - オーバーフロー⑦(uintの最大値チェック)

今回はuintの最大値に関する動作を確認していきます。

ソースコード

最大値のチェックを行うためのソースコードは下記の通りです。

[ソースコード]

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
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint public stockQuantity;

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint _addedQuantity);

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 0; // 最大値をチェックするため100を0に変更
}

/// 在庫の追加処理
function addStock(uint _addedQuantity) public onlyOwner {
// オーバーフローチェック
require(stockQuantity + _addedQuantity > stockQuantity);

AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

17行目で初期の在庫数を100から0に変更しています。

これは在庫の追加数としてuintの最大値を設定できるかどうかをテストするためです。

デプロイ

上記ソースのデプロイを行います。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

初期の在庫数(Stock quantity)が0であることが確認できます。

在庫の追加(最大値)

Add Stcok関数を使って、uintの最大値を設定します。

uintの最大値は115792089237316195423570985008687907853269984665640564039457584007913129639935です。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

問題なくuintの最大値が在庫数(Stock quantity)に設定されていることが分かります。

また画像一番下のイベント情報より正しい数値(uintの最大値)が引数として渡されていることも分かります。

在庫の追加(最大値+1)

次に、uintの最大値を超える数値を設定してみます。

[uintの最大値]+1の数値は115792089237316195423570985008687907853269984665640564039457584007913129639936になります。

[在庫数を追加]

[EXECUTE]ボタンを押すと、トランザクションを発行するための画面が表示されます。

ここでパスワードを入力し、[SEND TRANSACTION]ボタンを押したところ次のようなメッセージが表示されました。

[トランザクション実行画面]

どうやら256バイトを超える値は設定できないようです。

uint8の場合は、それを超える数値を設定できたので、データ型の制限ではなくSolidityの引数の制限だと思われます。(ただの推測です)

以上で、オーバーフローに関する動作確認とその対策に関する記事は終わりにします。

Ethereum(51) - オーバーフロー⑥(2回目の改善確認編)

前回修正したスマートコントラクトを使ってオーバーフローが解消されているかどうかを確認します。

デプロイ

デプロイを行います。

デプロイするのは前回改善したMarket Place Overflowです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

在庫数(Stock quantity)が100であることが確認できます。

在庫の追加

Add Stcok関数を使って、(前回NGとなった)在庫数257の追加を行います。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

前回はオーバーフローが発生したため在庫数が101となってしまいましたが、今回は問題なく357となりました。


念のためイベントをを確認し、正しい引数が渡されているかどうかを確認します。

[イベントの確認]

問題なく257が引数として渡されています。

まとめ

オーバーフローの対策として次の2点を改善しました。

  • 在庫数を追加するときに、追加後の在庫数が増えることを確認。
    (オーバーフローが発生すると追加後の在庫数が減ってしまうため)
  • 追加する在庫数と既存の在庫数のデータ型をuint8からuintに変更。
    (uint8の範囲を超える数値が引数として渡されると、意図しない数値に変換されてしまうため)

2つめの修正ですが、扱える数字が増えただけで根本的な解決にはなっていなような気がします。

指定したデータ型を超える数値を指定したら結局またオーバーフローが発生してしまうと考えられるからです。


次回はuintを超える数値を設定して動作確認を行ってみます。

Ethereum(50) - オーバーフロー⑤(2回目の改善編)

前回は、在庫追加数としてuint8の範囲を超える数値を設定してしまったため、とオーバーフローが発生し257が1に変換されてしまいました。

今回はこの問題を解消していきます。

ソースコード(改善2)

改善策としまして、在庫数を追加する関数(AddStock関数)の引数を、データ型uint8からuintに変更してみます。

uint8の範囲は0~255ですが、uintの範囲は0~2の256乗までとかなりの大きな数字となります。

[ソースコード]

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
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint public stockQuantity; // 在庫数 改善2 uint8 ⇒ uint

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint _addedQuantity); // 改善2 uint8 ⇒ uint

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 100;
}

/// 在庫の追加処理
function addStock(uint _addedQuantity) public onlyOwner { // 改善2 uint8 ⇒ uint
// オーバーフローチェック
require(stockQuantity + _addedQuantity > stockQuantity); // 改善1

AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

追加在庫数(_addedQuantity)の修正箇所は12行目と21行目で、uint8をuintに変更しています。

また追加在庫数と合わせて、現在の在庫数(stockQuantity)のデータ型もuintに変更しておきます。(4行目)


次回はこの改善でオーバーフローが解消しているかどうかの確認してみます。

Ethereum(49) - オーバーフロー④(改善確認編)

前回修正したスマートコントラクトを使ってオーバーフローが解消されているかどうかを確認します。

デプロイ

デプロイを行います。

デプロイするのは前回改善したMarket Place Overflowです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

在庫数(Stock quantity)が100であることが確認できます。

在庫の追加①

Add Stcok関数を使って、前回同様に在庫数を156増やしてみます。

確認ダイアログではgas(手数料)に5000000を設定しました。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

前回はオーバーフローが発生したため在庫数が0になってしまいましたが、今回は改善したので在庫の追加が行われず在庫数が100のままになっています。

いちおうオーバーフローが解消しています。


念のためトランザクションも確認します。

[トランザクションの確認]

Gas usedが指定した5000000となっており、最大手数料が消費されているのでトランザクションが失敗したことになります。

在庫の追加②

もう一度在庫の追加を行ってみます。今回は257追加してみます。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

なぜか追加処理が行われ在庫数が101と増えてしまっています。

イベントも確認してみます。

[イベントの確認]

追加の在庫数として257を設定したのですが1となっています。

どうしてでしょうか。

考察

在庫数を追加するAdd Stock関数に257を設定したにも関わらず1という引数が渡されてしまった原因は、引数のデータ型がuint8となっているためです。

データ型のuint8では、0~255までの数値しか表現することができず257が指定されると1に変換されてしまったのです。(桁落ち)

その結果、前回改善した判定が満みたされ在庫追加処理が正常に実行されたということになります。


次回はこの問題を解消するための再度対策を行います。

Ethereum(48) - オーバーフロー③(改善編)

前回オーバーフローが発生したソースを修正します。

ソースコード(改善1)

前回の動作確認では、在庫数(stockQuantity)を追加しているにも関わらず、オーバーフローが発生したため在庫数が減ってしまっていました。

そこで在庫追加後の数が元の在庫数を超えることを確認して、オーバーフローを防いでみます。

[ソースコード]

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
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint8 public stockQuantity; // 在庫数

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint8 _addedQuantity);

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 100;
}

/// 在庫の追加処理
function addStock(uint8 _addedQuantity) public onlyOwner {
// オーバーフローチェック
require(stockQuantity + _addedQuantity > stockQuantity); // 改善1

AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

23行目で追加後の在庫数が現在の在庫数より大きいことをチェックしています。

次回はこの改善でオーバーフローが解消しているかどうかの確認してみます。

Ethereum(47) - オーバーフロー②(実行編)

前回作成したスマートコントラクトを使ってオーバーフローの動作確認を行います。

アカウンとの役割

今回の動作確認では、アカウントの役割は重要ではないのですが、一応次のようにしておきます。

  • MAIN ACCOUNT (eth.accounts[0])
    コントラクト生成者。マイナー

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするのはMarket Place Overflowです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

在庫数(Stock quantity)が100であることが確認できます。

在庫の追加

Add Stcok関数を使って、在庫数を156増やしてみます。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

100 + 156 = 256 になるはずですが、0になってしまいました。

下記のイベント確認すると、在庫追加の引数としては問題なく156が渡されたことが分かります。

[イベントの確認]

ではなぜ在庫数が256にならずに0になってしまったのでしょうか。

計算が合わない理由

原因は在庫数(stockQuntity)がunit8で宣言されているためです。

uint8では8ビットの符号なし整数を表します。範囲としては0~255となります。

256になるとオーバーフローが発生し、0となってしまったのです。


次回はこの問題を解消するための対策を行います。

Ethereum(46) - オーバーフロー①(実装編)

オーバーフローは、スマートコントラクトならではの問題ではありませんが意外と見落としがちです。

オーバーフローが発生すると、データの不整合が発生するので注意が必要です。

ソースコード

オーバーフローを確認するためのサンプルソースは以下の通りです。

[ソースコード]

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
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint8 public stockQuantity; // 在庫数

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint8 _addedQuantity);

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 100;
}

/// 在庫の追加処理
function addStock(uint8 _addedQuantity) public onlyOwner {
AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

マーケットプレイスを想定したコントラクトで、在庫数の追加を行う関数のみを実装しています。

次回は、このスマートコントラクトを使ってオーバーフローになる場合の動作を確認します。

Ethereum(45) - 重要な情報の取り扱い②(実行編)

前回作成した重要な情報を扱うスマートコントラクトの動作確認を行います。

アカウントごとの役割

今回の動作確認では、アカウントの役割は重要ではないのですが、一応次のようにしておきます。

  • ACCOUNT3 (eth.accounts[3])
    コントラクト生成者。
  • ACCOUNT1 (eth.accounts[1])
    マイナー。

デプロイ

まずはデプロイを行います。デプロイアカウントはアカウント3です。

デプロイするのはSecretで、コンストラクタパラメータにはtestを設定します。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

さすがにこの画面からはsecretにどんな文字が設定されているかは確認できません。
(privateで宣言しているため)

トランザクションの確認

コントラクトを生成したときのハッシュを確認します。

[トランザクションの確認]

このトランザクション・ハッシュを引数にして、トランザクションのinputというフィールドを確認します。

gethコンソールから次のコマンドを実行します。

[トランザクションのinputフィールド確認]

1
2
> eth.getTransaction('0xd34e1aa86318225a66a77399f95a835b250ac75139c1c01eb2a80d3636238939').input
"0x6060604052341561000c57fe5b604051610263380380610263833981016040528051015b805161003690600090602084019061003e565b505b506100de565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061007f57805160ff19168380011785556100ac565b828001600101855582156100ac579182015b828111156100ac578251825591602001919060010190610091565b5b506100b99291506100bd565b5090565b6100db91905b808211156100b957600081556001016100c3565b5090565b90565b610176806100ed6000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637ed6c926811461003a575bfe5b341561004257fe5b610090600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965061009295505050505050565b005b80516100a59060009060208401906100aa565b505b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100eb57805160ff1916838001178555610118565b82800160010185558215610118579182015b828111156101185782518255916020019190600101906100fd565b5b50610125929150610129565b5090565b61014791905b80821115610125576000815560010161012f565b5090565b905600a165627a7a72305820548d2d70ba56a4626512317eed38e749eae8a73a9d5322c580ebab786f1c12d90029000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"

前半部分はコンパイルされたコントラクトのコードですが、後半部分のゼロに囲まれた474657374に注目してみます。

実は、この部分の最初の4はバイト数を表し、その直後の74657374(4バイト)はコントラクトで設定された文字列を表しています。

試しにこのデータをアスキーに変換してみます。

[アスキー変換]

1
2
> web3.toAscii("0x74657374")
"test"

なんとprivateで設定した文字列”test”が簡単に見れてしまいました。

文字列を更新

setSecret関数をコールして、文字列をkoushinに変更します。

[文字列の確認]

再度、トランザクションのハッシュから、inputフィールドを確認します。

[トランザクションのinputフィールド確認]

1
2
> eth.getTransaction('0x1bf2c055abc6e5d0f861c64c52c7a84419c4b80a41edbf954bce7c78b4147392').input
"0x7ed6c926000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076b6f757368696e00000000000000000000000000000000000000000000000000"

また後半部分のゼロに囲まれた76b6f757368696eに注目してみます。

最初の7はバイト数を表し、6b6f757368696e(7バイト)は更新した文字列を表しています。

このデータをアスキーに変換してみます。

[アスキー変換]

1
2
> web3.toAscii("0x6b6f757368696e")
"koushin"

更新した文字列を確認することができました。

まとめ

privateな変数を宣言しても、トランザクションの中身を確認すれば簡単にそのデータを確認できるということが分かりました。

Ethereumではトランザクションが暗号化されないため、中身を参照すればどんなデータか確認することができるのです。

個人情報のような重要な情報をステートに設定する場合は、暗号化するなどの処理を行うようにしましょう。