MARSというソフトがQNAPから昨年秋にリリースされていて、使い道はgoogle photoとwordpressのバックアップに使えそうです。
で、NASのディレクトリ再構築の機会にインストールしました。
に手順が記載されていますが、スケジュール設定すればほぼリアルタイムバックアップもできます。HBS3との違いはプロトコルが違うのと、バックアップ限定で同期処理はできないというのが機能面の違いでしょうか。
wordpressのバクアップ設定はこれから、
admin
la vie libre
MARSというソフトがQNAPから昨年秋にリリースされていて、使い道はgoogle photoとwordpressのバックアップに使えそうです。
で、NASのディレクトリ再構築の機会にインストールしました。
に手順が記載されていますが、スケジュール設定すればほぼリアルタイムバックアップもできます。HBS3との違いはプロトコルが違うのと、バックアップ限定で同期処理はできないというのが機能面の違いでしょうか。
wordpressのバクアップ設定はこれから、
admin
他のGolangでSQL文を使って作成したテーブルにGORMを使ってアクセスします。ポイントはテーブル名がデフォルトでは使えないので、Tabler interfaceのメソッドであるTableName()を実装してテーブル名を与えているところになるかと思います。
https://isehara-3lv.sakura.ne.jp/blog/2023/04/01/goのormであるgormを見てみる/
の再構成に過ぎませんが、最低限やりたいことはGORMで実現できそうです。
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"time"
"strconv"
)
type Product struct {
Uid string `gorm:"primaryKey"`
Name string
Time int
Stat int
}
var products []Product
//
// implements TableName of the Tabler interface
//
func (Product) TableName() string {
t := time.Now()
tableName := "tbl" + strconv.Itoa(t.Year()) + strconv.Itoa(t.YearDay())
return tableName
}
func main() {
db, err := gorm.Open(sqlite.Open("~~~path to the db~~~ /myfare.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&Product{}) // if you use Automigrate and change struct, it won't be reflected automatically
db.Debug().Find(&products) // SELECT * FROM tbl*****;
for i, p := range products{
//db.Model(&p).Update("Qty", 20)
fmt.Println(i, p)
}
}
admin
ORM(Object Relational Mapping)というのは、SQL文ではなくオブジェクト指向でデータベースを操作できる機能です。
Go言語ではGORMがもっともポピュラーなようなので、見てみましょう。
https://gorm.io/ja_JP/docs/index.html
にあるサンプルに多少手を入れて動作を理解しようとしたものです。
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Product struct {
gorm.Model
Code string
Price uint
Qty uint
}
/*
// gorm.Modelの定義
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}*/
var products []Product
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&Product{}) // if you use Automigrate and change struct, it won't be reflected automatically
// Create
db.Create(&Product{Code: "D42", Price: 100, Qty: 20})
db.Create(&Product{Code: "A42", Price: 300, Qty: 5})
// Read
var product Product
//db.First(&product, 1) // find product with integer primary key
db.First(&product, "code = ?", "D42") // find product with code D42
fmt.Println(product.ID, product.Code, product.Price, product.Qty)
// Update
db.Model(&product).Update("Price", 200) // update only pointer(*) reocord
fmt.Println(product.ID, product.Code, product.Price, product.Qty)
db.Model(&product).Update("Qty", 30) // *:initial Create record pointer
fmt.Println(product.ID, product.Code, product.Price, product.Qty)
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // non-zero fields
fmt.Println(product.ID, product.Code, product.Price, product.Qty)
db.Model(&product).Updates(map[string]interface{}{"Price": 500, "Code": "F42"})
fmt.Println(product.ID, product.Code, product.Price, product.Qty)
db.Model(&products).Where("ID = ?", 2).Update("Price", 1000) // search ID and update record
// Delete - delete product with condition
//db.Delete(&products, "Code = ?", "F42") // delete all of the code = "D42"
db.Find(&products) // SELECT * FROM Product;
for i, p := range products{
//db.Model(&p).Update("Qty", 20)
fmt.Println(i, p)
}
}
以下が実行結果になります。意外だったのは、db.Modelは現在のID(Primary key)のレコードに対して働くということ。
・実行一回目
1 D42 100 20
1 D42 200 20
1 D42 200 30
1 F42 200 30
1 F42 500 30
0 {{1 2023-04-01 09:25:58.10815 +0900 +0900 2023-04-01 09:25:58.110959 +0900 +0900 {0001-01-01 00:00:00 +0000 UTC false}} F42 500 30}
1 {{2 2023-04-01 09:25:58.10884 +0900 +0900 2023-04-01 09:25:58.111384 +0900 +0900 {0001-01-01 00:00:00 +0000 UTC false}} A42 1000 5}
・実行二回目
3 D42 100 20
3 D42 200 20
3 D42 200 30
3 F42 200 30
3 F42 500 30
0 {{1 2023-04-01 09:25:58.10815 +0900 +0900 2023-04-01 09:25:58.110959 +0900 +0900 {0001-01-01 00:00:00 +0000 UTC false}} F42 500 30}
1 {{2 2023-04-01 09:25:58.10884 +0900 +0900 2023-04-01 09:26:30.669874 +0900 +0900 {0001-01-01 00:00:00 +0000 UTC false}} A42 1000 5}
2 {{3 2023-04-01 09:26:30.666797 +0900 +0900 2023-04-01 09:26:30.669524 +0900 +0900 {0001-01-01 00:00:00 +0000 UTC false}} F42 500 30}
3 {{4 2023-04-01 09:26:30.667772 +0900 +0900 2023-04-01 09:26:30.667772 +0900 +0900 {0001-01-01 00:00:00 +0000 UTC false}} A42 300 5}
データベースは存在しなければgorm.Opendで
自動作成されます。
db.Createがレコードの新規作成、db.Modelがレコードの更新、使ってませんがdb.Deleteがレコード削除です。条件付けはwhere句を使うのでSQLと類似しているように思います。またdb.Firstはレコードを一個だけ(SELECT * FROM products ORDER BY id LIMIT 1;と等価)引っ張ってくるしde.Findはselect*from相当です。
gorm.Modelを構造体に記載するとプライマリーキーや時刻が自動的にレコードに追記されますが、いまいち余計な感じもします。またdb.AutoMigrateはテーブル作成後に構造体を変更しても反映されないようなのでこれも注意が必要でしょう。したがってサンプルのままに使用することはそれほどないように思います。
gorm.Modelを使わなければ、例えば以下のようにP-Keyをセットすれば良いでしょう。
type Product struct {
ID int `gorm:"primaryKey"`
Code string
Price uint
Qty uint
}
いずれにしてもSQL文を作成するよりは間違いが入り込みづらいように見えるので、できる限りORMを使うのが良さそうです。
P.S. 2023/4/2
テーブル名ってどうなるかと思ったら、構造体の複数形(ドキュメントの説明:構造体 User
の場合、対応するテーブル名は規約により users
となります。)が使われるようです。テーブル名をダイナミックに設定したいと思ったらちょっと不便かもしれない。
sqlite> select*from products;
1|F42|500|30|2023-04-02 10:54:48.693791+09:00
2|A42|1000|5|2023-04-02 10:54:48.694968+09:00
3|F42|500|30|2023-04-02 14:13:44.198783+09:00
4|A42|300|5|2023-04-02 14:13:44.200078+09:00
テーブル名はTabler
インターフェイスの実装で変更できるようですね。
https://gorm.io/ja_JP/docs/conventions.html
以下を追加すると、
func (Product) TableName() string {
t := time.Now()
tableName := "tbl" + strconv.Itoa(t.Year()) + strconv.Itoa(t.YearDay())
return tableName
}
テーブル名の変更指定ができました。
sqlite> .tables
products tbl202392
2. SQL文を出力したい
通常はSQL文は隠蔽されますが、Debug()関数を挟んでやれば、SQL相当のステートメントを出力してくれます。
db.Debug().Find(&products) // SELECT * FROM Product;
for i, p := range products{
fmt.Println(i, p)
}
こんな感じで、
2023/04/02 20:21:46 ~~~/orm.go:71
[0.118ms] [rows:10] SELECT * FROM `tbl202392`
~途中省略
5 { ゲスト5 1680422277 0}
6 { ゲスト6 1680422277 0}
7 { ゲスト7 1680422277 0}
8 { ゲスト8 1680422277 0}
9 { ゲスト9 1680422277 0}
admin
Mifareカードリーダーを使ったチェックインシステムを
GolangとNode.js使って雛形もどきを作ってみた。Node.jsはwebアプリ雛形作成のexpress-generatorを使っています。
GolangでSQLite3のデータベーステーブル作成とアップデート、Node.jsはwebサーバー起動してviewを作成しています。
・index.js:データベースのテーブルから読み出したチェックイン情報を読み出して、ページ(view)の必要情報を用意します。データベース読み込みは非同期処理なので、async/awaitで同期化します。
・index.pug:index.html作成のテンプレートです。pugファイルにはJavaScriptの記述もできて繰り返し処理が簡単にできるから便利です。
・style.css:スタイルシート、本来のディレクトリから一個上の階層に移動してあります。GitHubへの転送がそうしないとうまくできなかったから。
・serial.go:M5StackC plusからのカード読み取り情報を受け取りjsonのuidと名前対応情報から手ブルを作成。テーブル名は年号と1月1日からの経過日を組み合わせたものにしています。
コードは分散していますが以下のリンクから、
https://github.com/chateight/golang/tree/master/serial
P.S. 2023/4/1
*_tool.jsはデバッグ用のコードです。
カードリーダーのM5StackC plusのコードなどの情報は、
https://github.com/chateight/rfid
admin
便利なようで結構不便なJavaScriptのシングルタスクを有効に使おうとするイベント処理。非同期処理は簡単な使い方では不便でSQLite3のDBアクセスライブラリには同期型も存在するらしい。
ユーザーが多いだろうから非同期型のライブラリでなんとかしようとするとPromise処理が必要になります。最新の機能ではawait/asyncになりますが。以下はexpress-generatorで発生されたwebページのディスパッチ処理を行うindex.jsにデータベースアクセス処理を同期させて追加したコードです。
<index.js>
var express = require('express');
var router = express.Router();
let nameArray = new Array();
/* GET home page. */
router.get('/', function(req, res, next) {
var response = Math.floor(Math.random() * (100 - 1)) + 1;
let t1 = new Date();
async function dbReadExec(){
try{
nameArray = await dbRead();
let t2 =new Date();
console.log("dbReadEec",nameArray, t2 - t1);
res.render('index', { title: 'Express', data: response, db: nameArray});
} catch (err){
console.log('error');
} finally {
console.log("done");
}
}
dbReadExec();
});
//
// DB handler
//
function dbRead(){
return new Promise((resolve, reject) => {
const sqlite3 = require("sqlite3");
const db = new sqlite3.Database("/Users/username/go/src/serial/myfare.db");
// calc file name
let d = new Date();
let year = d.getFullYear();
let month = d.getMonth();
let day = d.getDate();
let date1 = new Date(year, 0, 0);
let date2 = new Date(year, month, day)
let behind = Math.floor((date2 - date1) / (24*3600*1000)); // convert milisec to day
nameArray = []; // clear Array
db.serialize(() => {
db.each("select * from tbl" + year + behind + " where stat=0", (err, row) => {
if (err) {
reject(err);
}
//console.log(`${row.id} ${row.name} ${row.time} ${row.stat}`);
nameArray.push(row.name);
resolve(nameArray);
})
db.close();
});
});
}
module.exports = router;
serialize()したつもりでも、resolve(nameArray);をdb.close()の後にするとうまく動きません。ここも非同期で動くから?ループ内でresolve()を複数回発行することになりますが、これはthenがchainされて最後のresolve()が有効になるようです。データ量が少ないなら、db.each()ではなくdb.all()で一回で返せば良さそうに思いますが。
let t2 =new Date();
console.log("dbReadEec",nameArray, t2 - t1);
はデータベース処理時間計測のためのタイムスタンプです。二回目以降はJavaScriptのキャッシュが有効だろうから高速です。
シングルタスク言語というのはちょっと複雑な構造のコードを書こうとすると不便なところが目立つように感じます。
P.S. 2023/3/28
db.each部分だけをdb.allに置き換え、
db.all("select * from tbl" + year + behind + " where stat=0", (err, rows) =>{
rows.forEach(row => nameArray.push(row.name));
//console.log(nameArray);
resolve(nameArray);
})
こちらの方がスマートでしょう。
admin
ラズパイにはExpress-Generatorをインストールしていましたが、MacBookにもインストールして久しぶりに触っています。Express-Generatorはwebアプリ雛形作成のためのツールの一つです。webアプリ作成のためには理解すべき点がいくつかありますが、動的なページを作成するためのデータの与え方はその一つです。
以下は関連するディレクトリを表示していますが、index.jsでviews配下のindex.pugのコンテンツを作るようになっています。
index.jsソースの一部(dbName())は記載していませんが、データベースからの読み出しを行なっています。本タイトルと直接の関係はないので省略。
var express = require('express');
var router = express.Router();
var nameList = "";
/* GET home page. */
router.get('/', function(req, res, next) {
var response = Math.floor(Math.random() * (100 - 1)) + 1;
dbName();
const wait = () => {
res.render('index', { title: 'Express', data: response, db: nameList});
}
setTimeout(wait, 3);
});
res.render()の第二引数以下がindex.pugに渡されてhtmlのコンテンツを構成しています。雛形ではtitle一個しかありませんが、カンマ区切りで任意の個数を追加できます。ここでは1~100までの範囲の乱数をdataとして、データベースから読み出したデータを変数名dbでindex.pugに渡しています。
index.pug
extends layout
block content
h1= title
p Welcome to #{title}
p #{data}
p #{db}
SQLite3のデータベースアクセスも非同期で実行されるので、ページ作成が先行して実行されてdbデータの供給が間に合わないので、仮に時間待ち3msを入れていますが、あくまでデバッグ専用でいずれ修正の予定。
admin
GolangにはtimeパッケージにYearDay()という年初からの連続で今日が何日目かを返してくれる関数があってそれをファイル名(tbl + 西暦年 + 連続何日目)に使っています。1日に2個以上テーブル作る可能性がないから、これで一意にファイル名を決定できます。
でもNode.jsで同じファイル名を指定しようとするとこのような関数はないので、計算が必要になります。なぜNode.jsで必要かというとGolangで作成したSQLiteのテーブルにNode.jsからアクセスしたいからですが。
Golangでのテーブル作成、年号と年初からの経過日でファイル名を作成しています。以下のコードの一番下の行でターブル作成。
date := strconv.Itoa(t.Year()) + strconv.Itoa(t.YearDay())
if del {
cmd := "drop table if exists tbl" + date
_, err := db.Exec(cmd)
if err != nil {
fmt.Println("db table drop error")
}
}
// table exist check
cmd := "select*from tbl" + date
_, table_check := db.Query(cmd) // checked by using return code
if table_check != nil {
cmd = "create table if not exists tbl" + date + "(id string primary key, name, time int, stat int)"
JavaScriptでのテーブル名の作成スクリプト
// calc file name
let d = new Date();
let year = d.getFullYear();
let month = d.getMonth();
let day = d.getDate();
let date1 = new Date(year, 0, 0);
let date2 = new Date(year, month, day)
let behind = Math.floor((date2 - date1) / (24*3600*1000)); // convert milisec to day
db.serialize(() => {
db.each("select * from tbl" + year + behind + " where stat=0", (err, row) => {
console.log(`${row.id} ${row.name} ${row.time} ${row.stat}`);
})
});
Math.fllor():端数切り捨てはなくても大丈夫のようですが、入れておいて実害はないだろうから。
admin
Golangで作成したSQLiteのテーブルをnode.jsからアクセスしてみようと思ったが、前提になるnpmが起動できないし、nodeも起動できない。
% node
dyld[71646]: Library not loaded: /opt/homebrew/opt/icu4c/lib/libicui18n.71.dylib
Referenced from: <66A3E1EC-93E2-36D1-889D-02EE5C192FCC> /opt/homebrew/Cellar/node/18.11.0/bin/node
Reason: tried: ‘/opt/homebrew/opt/icu4c~~~~以下省略
解決方法は、
から、
% brew reinstall icu4c
で解決。
・Node.jsからSQLiteへアクセスするドライバーをインストール
% npm install sqlite3
・すでに存在しているテーブルにアクセスしてみる、
//
// DB handler
//
'use strict';
const sqlite3 = require("sqlite3");
const db = new sqlite3.Database("./myfare.db");
db.serialize(() => {
db.each("select * from tbl202382 where stat=0", (err, row) => {
console.log(`${row.id} ${row.name}`);
})
});
db.close();
do.run()が非同期実行なのでsierialize()が必要とのことですが、データ更新してなくて読んでるだけで並行処理されるものがないから実はserialize()はこのケースでは不要。
しかし実行すると、
Error: Cannot find module ‘sqlite3’
対策はパスを通すことらしいので、
% export NODE_PATH=`npm root -g`
で、
% echo $NODE_PATH
/opt/homebrew/lib/node_modules
にsqlite3は存在していますが、
% which sqlite3
とは違うディレクトリなので実はシンボリックリンクを使っているのかもしれない。
ともかくもSQLite3のテーブルアクセスはできた。
GolangでDBドライバー使ってコードからテーブルの存在有無を確認する方法です。SQLコマンド打てば簡単ですが、コードでチェックするというのはどうやるかしばらく悩みました。以下のstackoverflowから引用しました。
https://stackoverflow.com/questions/56915022/check-if-database-table-exists-using-golang
動作環境でimportしているライブラリ(DBアクセスで必要なのはsqlとsqlite3の2個)、go-sqlite3は使わないけどインポートだけはしないとコンパイルできません。
go-sqlite3のブランクインポートが必要な理由は、
https://maku77.github.io/p/kgzfwdt/
になります。
import (
"bufio"
"database/sql"
"encoding/json"
"errors"
"fmt"
_ "github.com/mattn/go-sqlite3"
"log"
"os"
"strconv"
"time"
"go.bug.st/serial"
"go.bug.st/serial/enumerator"
)
以下がテーブル存在をチェックしているコードになりますが、dateはフィアル名の一部ですね。戻りのerrorコードがnilならテーブル存在、それ以外ならテーブルが存在しないことになります。
// table exist check
cmd := "select*from tbl" + date
_, table_check := db.Query(cmd) // checked by using return code
if table_check != nil {
方法は別にSQLite限定ではなくて他のDBでも同じだろうと思います。
admin