Pythonから Btrieve 2 API を利用したプログラミング手法
今回は、Zen データベースNoSQLアクセス専用のAPI「Btrieve 2 API」を使って、PythonからZen データベースへアクセスする手順を説明します。サンプルプログラムを引用しながら、より具体的にわかりやすく説明します。「Btrieve 2 API」の概要につきましては、はじめてのActian Zenシリーズ2「Pythonからのデータアクセス」編をご参考ください。
*目次*
1.事前準備
はじめてのActian Zenシリーズ2「Pythonからのデータアクセス」編をお読みいただき、Actian Zen v15 SP2とPython 3.11.xの環境を用意し、以下2つのファイルを任意のフォルダに保存します。 (当記事内では、保存先を「C:¥MyPrograms」として説明します。)
(1) _btrievePython.pyd
(2) btrievePython.py
さらに、当ブログ内で用いるサンプルプログラムを以下よりダウンロードし、同じく【MyPrograms】フォルダに保存します。
https://s3-ap-northeast-1.amazonaws.com/agtech.co.jp/download/blog/Sample_Btrv2.zip
2.サンプルプログラムの概要
(1) 日付、文字、数値のデータをテーブルに「登録」します。
(2) テーブルにインデックスを設定し、登録したデータの「検索」を行います。
(3) 登録したデータに対し「更新」を行います。
3.サンプルデータと作成するテーブル内容
表1に示す5項目(フィールド)、5行(レコード)のデータを、Python上でBtrieve 2 APIを用いて、Zen データベースへ登録します。
売上日 | 売上月 | 売上年 | 商品名 | 売上金額 |
5 | 9 | 2020 | スイカ | 10000 |
15 | 9 | 2020 | トウモロコシ | 20000 |
25 | 9 | 2020 | スイカ | 30000 |
5 | 10 | 2020 | ナス | 40000 |
15 | 10 | 2020 | ナス | 50000 |
表 1 テーブルに登録するデータ
Zen データベース登録後のテーブル内容とレイアウトは、表2、表3のとおりです。 Pythonで扱う値は年、月、日をそれぞれ分けて扱いますが、実際に格納するデータベースでは年月日をひとまとめで扱う DATE 型で登録するため、次のようにテーブルを作成します。
売上年月日 | 商品名 | 売上金額 |
9/5/2020 | スイカ | 10000 |
9/15/2020 | トウモロコシ | 20000 |
9/25/2020 | スイカ | 30000 |
10/5/2020 | ナス | 40000 |
10/15/2020 | ナス | 50000 |
表 2 Zen データベース登録後のテーブル内容
フィールド名 | サイズ | Btrieve型 | SQL型 |
売上年月日 | 4 | DATE | DATE |
商品名 | 30 | STRING | CHAR(30) |
売上金額 | 4 | INTEGER | INTEGER |
表 3 Zen データベース登録後のテーブルレイアウト
4.サンプルプログラムの解説
※手順(2)「ファイルの格納先、レコードフォーマット、レコード長の設定」、および、手順(5)「データの登録」はとくに重要です。
※サンプルプログラムには、コマンド実行後のエラー処理も入っていますが、解説を省略します。
(1)Btrieve 2 APIを実行するために必要なモジュールをインポート。
import os os.add_dll_directory (“C:¥Program Files¥Actian¥Zen¥Bin”) import sys import struct import btrievePython as btrv |
【解説(1)-1】「C:¥Program Files¥Actian¥Zen¥Bin」について:
Actian Zen のインストール先を変更されている場合は、パスの変更が必要です。
【解説(1)-2】「import struct」について:
テーブルでは、値をバイナリで保持しています。Python の Structパッケージは、文字や数字からバイナリへ変換したり、反対にバイナリから文字や数字へ変換したりするために使用します。
※具体例
●テーブルに「登録」するときは、文字や数字からバイナリへ変換してから登録を実行します。
●テーブルを「検索」するときは、検索ワードをバイナリへ変換してから検索を行い、検索結果をバイナリから文字や数字に変換してから表示します。
【解説(1)-3】「import btrievePython as btrv」について:
Btrieve 2 API関連のライブラリです。
(2)データの格納先、レコードフォーマット、レコードの長さ、キーフォーマットを設定(重要)
btrieveFileName = “C:¥MyPrograms¥SALES.mkd” recordFormat = “<BBH30si” recordLength = 38 keyFormat = “<30sBBH” |
【解説(2)-1】「btrieveFileName = “C:¥MyPrograms¥SALES.mkd”」について:
データの格納先を指定します。
【解説(2)-2】「recordFormat = “<BBH30si”」について:
レコードフォーマットを指定します。「<」はリトルエンディアンと呼ばれるものです。リトルエンディアン、ビッグエンディアン(ビッグエンディアンは「>」と指定します)の違いは、データを格納する順番で、CPUの種類によって決まっています。現在多くのPCで使用されているIntel, AMD系のCPUは、リトルエンディアンです。
表4の赤字部分は、各フィールドのフォーマットです。「B」、「H」、「30s」、「i」は、PythonのStructパッケージのフォーマットです。
売上日 B |
売上月 B |
売上年 H |
商品名 30s |
売上金額 i |
5 | 9 | 2020 | スイカ | 10000 |
15 | 9 | 2020 | トウモロコシ | 20000 |
25 | 9 | 2020 | スイカ | 30000 |
5 | 10 | 2020 | ナス | 40000 |
15 | 10 | 2020 | ナス | 50000 |
表 4 各フィールドに割り当てたフォーマットとサイズ(赤字部分)
表5は、Structパッケージで使用できるフォーマットの一部を示したものです。
その他のフォーマットに関しては、以下Pythonのサイトをご確認ください。
https://docs.python.org/ja/3/library/struct.html/
フォーマット | C言語の型 | Pythonの型 | 標準サイズ (バイト) |
数値の範囲 |
b | signed char | 整数 | 1 | -128~127 |
B | unsigned char | 整数 | 1 | 0~255 |
h | short | 整数 | 2 | -32768~32767 |
H | unsigned short | 整数 | 2 | 0~65535 |
i | int | 整数 | 4 | -2147483648~2147483647 |
I | unsigned int | 整数 | 4 | 0~4294967295 |
s | char[] | bytes | – | – |
表 5 Structパッケージにおけるフォーマット
(1) BtrieveインターフェイスにおけるDATE(日付型)
Btrieveインターフェイスにおける日付型は、日、月、年の順番で構成し、日を1バイト、月を1バイト、年を2バイト、計4バイトで定義します。日付は正の値しかないことから、Structパッケージにおけるフォーマットを以下のように定義します。
- 売上日:B(1バイト)
- 売上月:B(1バイト)
- 売上年:H(2バイト)
(2) BtrieveインターフェイスにおけるSTRING(文字型)
Btrieveインターフェイスにおける文字型は、必要なバイト数を定義します。値が定義したバイト数に満たない場合は、定義したバイト数になるまで空白を埋め込む必要があります。
今回のサンプルプログラムでは、商品名を最大30バイトとします。Structパッケージにおけるフォーマットを以下のように定義します。
- 商品名:30s(30バイト)
(3) BtrieveインターフェイスにおけるINTEGER(整数型)
Btrieveインターフェイスにおける整数型は、符号付きで保持します。
今回のサンプルプログラムでは、金額が32767より大きいことを想定します。Structパッケージにおけるフォーマットを以下のように定義します。
- 売上金額:i(4バイト)
長さ(バイト) | 値の範囲 |
1 | 0~255 |
2 | -32768~32767 |
4 | -2147483648~2147483647 |
8 | -9223372036854775808~9223372036854775807 |
表 6 BtrieveインターフェイスにおけるINTEGER型
Btrieve インターフェイスにおけるデータ型は、以下をご参考ください。
https://www.agtech.co.jp/products/actian/docs_portal/Zen/15.2/index.html#page/sqlref%2Fsqldtype.htm%23ww136871
【解説(2)-3】「recordLength = 38」について:
1レコードの長さを指定しています。テーブルは固定長です。
5つのフィールドのサイズ(バイト数)を合計し、38(1+1+2+30+4=38)と定義しています。
【解説(2)-4】「keyFormat = “<30sBBH"」について:
キー(インデックス)フォーマットを指定しています。
サンプルプログラムでは、キーが商品名と売上年月日の2つのセグメントにわかれています。
商品名が1番目のセグメント、売上年月日が2番目のセグメントです。
※後ほど説明する商品名の検索や、検索結果を売上年月日順に表示するために使用します。
(3)セッションの開始、ファイル(テーブル)を作成。
# セッションの開始 btrieveClient = btrv.BtrieveClient() # ファイルの属性 btrieveFileAttributes = btrv.BtrieveFileAttributes() rc = btrieveFileAttributes.SetFixedRecordLength(recordLength) # ファイルを作成 rc = btrieveClient.FileCreate(btrieveFileAttributes, btrieveFileName, btrv.Btrieve.CREATE_MODE_OVERWRITE) |
レコード長38(バイト)のファイルが、C:¥MyPrograms¥SALES.mkdに作成されます。
【解説(3)-1】「CREATE_MODE_OVERWRITE」について:
CREATE_MODE_OVERWRITEによって、実行するたびにテーブルが再作成されます。
(4)インデックスの設定(商品名)・・・第1セグメント
rc = btrieveKeySegment.SetField(4,30, btrv.Btrieve.DATA_TYPE_CHAR) rc = btrieveIndexAttributes.AddKeySegment(btrieveKeySegment) |
【解説(4)-1】
商品名に対して、インデックスを設定しています。
売上日 (1バイト) |
売上月 (1バイト) |
売上年 (2バイト) |
商品名 (30バイト) |
売上金額 (4バイト) |
(5)インデックスの設定(売上年月日)・・・第2セグメント
rc = btrieveKeySegment.SetField(0,4, btrv.Btrieve.DATA_TYPE_DATE) rc = btrieveIndexAttributes.AddKeySegment(btrieveKeySegment) rc = btrieveFile.IndexCreate(btrieveIndexAttributes) |
【解説(5)-1】
売上日、売上月、売上年の3つのフィールドを1つのフィールドとみなし、DATE(日付型)としてインデックスを設定しています。
売上日 (1バイト) |
売上月 (1バイト) |
売上年 (2バイト) |
商品名 (30バイト) |
売上金額 (4バイト) |
【解説(5)-2】
「rc = btrieveFile.IndexCreate(btrieveIndexAttributes)」により、商品名と売上年月日で、インデックスが作成されます。商品名での検索や、検索結果を売上年月日順に表示するために使用します。
※設定したインデックスは、デフォルトの設定では、最初に設定したインデックスが「INDEX_1」と命名され保存されます。(デフォルトの設定では、さらにインデックスを設定した場合、「INDEX_2」、「INDEX_3」と命名され保存されます。)
(6)登録実行(重要)
データは、Python上で、配列に格納されます。
def ljust_bytes_SJIS(bytelength,inputString): byteLengthofInputString = len(inputString.encode(‘SJIS’)) charLengthofInputString = len(inputString) outputString = inputString.ljust(bytelength – byteLengthofInputString + charLengthofInputString).encode(‘SJIS’) return outputString) |
【解説(6)-1】def ljust_bytes_SJIS(bytelength,inputString): について:
第1引数はバイト数、第2引数は文字列です。
指定文字列を左詰めにし、値の長さをバイト数でカウント後、残りを指定バイト数になるまで空白を埋め込み、Shift-jisからバイト型へ変換する関数です。
※CHAR型(文字型)の場合、値の長さがバイト数に満たない場合は、残りを空白で埋め込み、Shift-jisからバイト型へ変換する必要があります。
※日本語版Windows環境での実行を想定しています。日本語版Windows環境の場合、Zen データベースのデフォルトのエンコードがShift-jisとなるため、encode(‘SJIS’)と設定しています。
(ご参考)ljustメソッドは、文字列の値の長さを文字数でカウントし、残りをバイト数で埋め込みます。
例)30バイトの文字列に「スイカ」を登録する場合:
“スイカ”.ljust(30)と指定しますと、文字数の「3」が長さとしてカウントされ、残り「27」バイトが空白で埋め込まれます。本来「スイカ」は「6」バイト数のため、計33(=6+27)バイトの文字列が生成されます。
「def ljust_bytes_SJIS(bytelength,inputString)」では、文字数ではなくバイト数で文字列の長さをカウントし、指定したバイト数になるまで、空白が埋め込まれます。
「ljust_bytes_SJIS(30, “スイカ”)」により、「スイカ」6バイトに続いて、空白24バイトが埋め込まれ、計30バイトの文字列が生成されます。
record = struct.pack(recordFormat, 5, 9, 2020, ljust_bytes_SJIS(30, “スイカ”), 10000) rc = btrieveFile.RecordCreate(record) record = struct.pack(recordFormat, 15, 9, 2020, ljust_bytes_SJIS(30, “トウモロコシ”), 20000) rc = btrieveFile.RecordCreate(record) record = struct.pack(recordFormat, 25, 9, 2020, ljust_bytes_SJIS(30, “スイカ”), 30000) rc = btrieveFile.RecordCreate(record) record = struct.pack(recordFormat, 5, 10, 2020, ljust_bytes_SJIS(30, “ナス”), 40000) rc = btrieveFile.RecordCreate(record) record = struct.pack(recordFormat, 15, 10, 2020, ljust_bytes_SJIS(30, “ナス”), 50000) rc = btrieveFile.RecordCreate(record) |
【解説(6)-2】record = struct.pack(…) について:
指定したレコードフォーマットに基づいて、文字や数字がバイナリに変換されます。
【解説(6)-3】ljust_bytes_SJIS(30, “スイカ”) について:
「スイカ」(6バイト)のあと、24バイトの空白が埋め込まれ、30バイトの文字列が作成されます。
【解説(6)-4】rc = btrieveFile.RecordCreate(record) について:
※SQLのようなCommit処理はありません。テーブルに即時登録されます。
(7)検索、更新処理
サンプルプログラムには、登録後のテーブルに対して、1) 商品名による検索、2)検索結果表示後に売上金額を更新、の2つの機能が入っています。処理の流れを解説いたします。
find_name = input(‘\n検索したい商品名を入力してください。(処理を終了するには「Q」を入力します。): ‘ ) key = struct.pack(keyFormat, ljust_bytes_SJIS(30, find_name), 0, 0, 0) |
【解説(7)-1】
キーの第1セグメント(=商品名)に、検索したい商品名を設定します。 商品の売上年月日の一番古い日付から読み込むために、キーの第2セグメント(売上日、売上月、売上年)に0を設定します。
readLength = btrieveFile.RecordRetrieve(btrv.Btrieve.COMPARISON_GREATER_THAN_OR_EQUAL,btrv.Btrieve.INDEX_1, key, record) |
【解説(7)-2】
商品がデータベースにある場合、その商品の最初の日付を読み込みます。
recordUnpacked = struct.unpack(recordFormat, record) readLength = 0 if (recordUnpacked[3].strip() == find_name.strip().encode(‘SJIS’)): print(‘商品名が一致するレコードが見つかりました。 売上年:’, recordUnpacked[2], ‘売上月:’, recordUnpacked[1], ‘売上日:’, recordUnpacked[0], ‘商品名:’, recordUnpacked[3].decode(‘SJIS’), ‘売上金額:’, recordUnpacked[4]) |
【解説(7)-3】
商品名をバイナリから文字へ変換し、先頭で読み込んだレコードと商品名が一致するか比較します。 商品名が一致している場合、売上年月日、商品名、売上金額がコマンドライン上に表示されます。
new_uriage = input(‘\n売上金額を更新するには、新売上金額を入力してください。(更新しない場合、「N」を入力します。)’ ) if new_uriage.lower() != ‘n’: record = struct.pack(recordFormat, recordUnpacked[0], recordUnpacked[1], recordUnpacked[2], recordUnpacked[3],int(new_uriage)) rc = btrieveFile.RecordUpdate(record) |
【解説(7)-4】
売上金額をコマンドラインに入力すると、売上金額が更新されます。
readLength = btrieveFile.RecordRetrieveNext(record, 0) |
【解説(7)-5】
次のレコードを読み込んで同じ商品名であれば、処理を繰り返しますが、次の商品になった場合には商品名入力に戻ります。
rc = btrieveClient.FileClose(btrieveFile) |
5.NoSQL / SQLでのハイブリッド運用
NoSQLアクセス専用のAPI「Btrieve 2 API」で作成したテーブルは、SQLによるリレーショナルアクセスも可能です。
(1) Windowsのスタートメニューより、Actian Zen 15 > Zen Control Centerおよびドキュメントにて、Zen Control Center(以下「ZenCC」とします。)を起動します。
(2) データベースを新規作成します。
・データベース名:SALES
・場所:データの格納先に指定したパス(C:¥MyPrograms)
(3)作成したデータベース「SALES」を選択し、画面上部のツールバーより、【新しいSQLドキュメントを作成】アイコンをクリックします。
(4)データベースの選択画面で「SALES」が選択されていることを確認し、【OK】ボタンを押下します。
(5)以下のソースを画面上に貼り付け、画面上部のツールバーより、【すべてのSQLステートメントを実行】をクリックします。
SET TRUENULLCREATE = OFF; CREATE TABLE “SALES” IN DICTIONARY USING ‘SALES.MKD’ ( “売上年月日” DATE NOT NULL, “商品名” CHAR(30) NOT NULL, “売上金額” INTEGER NOT NULL); SET TRUENULLCREATE = ON; CREATE UNIQUE INDEX “インデックス0” IN DICTIONARY ON “SALES” ( “商品名” , “売上年月日” ); |
(6)ZenCC上にSALESテーブルの内容が表示され、SQLによるリレーショナルアクセスが可能となります。
データの件数が多くなればなるほど、NoSQLアクセス(Btrieveインターフェイス)によるデータ登録が効果を発揮します。データの登録はNoSQLアクセスで、その後の複雑な検索やクエリはSQLアクセスでといった、用途に応じたハイブリッドな運用を行うことができます。
評価版のお申込みはこちらから。(製品版と同等機能を30日間無料でお使いいただけます。)
https://www.agtech.co.jp/actian/zen/v15/trial/
AGBPプログラムに加入いただきますと、1年間無料でお使いいただける開発用ライセンスをお申込みいただけます。AGBPプログラムに関しましては、こちらをご参考ください。
https://www.agtech.co.jp/actian/partner/agbp/