PHPからSQLite3を使うための設定

Golangで作ったSQLite3のデーターベースをPHPからアクセスするためのメモです。初期状態ではSQLite3は使えないのでインストールと初期設定が必要です。

・実行環境と版数

OS : Raspberry PI zeroのRaspbian

$ php –version

PHP 7.3.31-1~deb10u7 (cli) (built: Jun 17 2024 21:48:38) ( NTS )

$ apachectl -v

Server version: Apache/2.4.38 (Raspbian)

 

・sqlite3 driverのインストール

$ sudo apt install php-sqlite3

インストール確認は、$ php -mで、

sqlite3 // これが見えればOK

 

・Apacheの再起動

これをしないとPHPコマンドからのスクリプト起動はうまくいくが、Apacheとはまだ連携していないのでブラウザ経由でSQLite3へのアクセスはできない。(しばらくハマった)

$ sudo service apache2 restart

ちなみに該当のコードは以下の通り、

<?php
$file = 'presenters_list.json';

if (file_exists($file)) {
    $json_data = file_get_contents($file);
    $presenters = json_decode($json_data, true);
    
    // ---------------------------------------------------
    // to access SQLite3 data (myfare app) and pull Ninja names who will make a presentaion
    $db_name = '/home/pi/myfare/myfare.db';
    $mifare = '';
    // to calculate a table name for the sqlite3 data base
    $year = date('Y');
    // leap year check
    if ($year%4 == 0){
        $show_date = ($year - 1).'-12-31';
    } else{
        $show_date = $year.'-1-1';
    }
    $show_date_timestamp = strtotime($show_date);
    $past_days = intval((abs(time() - $show_date_timestamp)) / (60 * 60 * 24));
    $tbl_name = "tbl".$year.$past_days;
    //$tbl_name = 'tbl2023108';   // tbl2023108 : this line is only for debugging
    //query names whose pressentation status is ON

    $db = new SQLite3($db_name);   
    $query_name = 'SELECT name FROM '.$tbl_name.' where presentation="1";';
    // to check if the table is available and issue query if exists
    $sql = "SELECT name FROM sqlite_master WHERE type='table' AND name="."'$tbl_name'";
    $result = $db->query($sql);

    if ($result->fetchArray()) {
        $result = $db->query($query_name);

        if (!empty($result)) {
            while ($row = $result->fetchArray()) {
                $mifare = $mifare.json_encode($row['name'].'@mifare').',';
            }
        }
        } else {}
    $db->close();
    // end of myfare data hundle code
    // ---------------------------------------------------
    
    if ($presenters !== null) {
        // to check if there is any name on presenter_list
        if (json_encode($presenters)== '[]'){
            echo '['.substr($mifare, 0, -1).']';
        }else{
            echo '['.$mifare.substr(json_encode($presenters), 1);
        }
        
    } else {
        echo json_encode(array('error' => 'error occured during file analysis'));
    }
} else {
    echo json_encode(array('error' => 'could not find the file'));
}
?>

 

admin

SQLite3で指定できるデータ型

Myfare cardを追加してテーブル作成しようとしたら、特定のカードのテーブルの値がstring指定したはずなのに”Inf”になってしまった。Infとはおそらく無限大ということだろうからstring型を指定しても機能しないらしい。

で調べるとSQLiteで指定できる型にはstringは無い。

https://www.javadrive.jp/sqlite/type/index1.html

おそらくstringを指定しても整数扱いとなってオーバーフローしてしまったらしい。

string -> text

int -> integer

に変更して作り直してうまくいっているようです。GORMで指定する構造体ではこのように変更しようがない(おそらくGORMが自動で変換する)から、テーブル作成時のSQLコマンドだけでの対応。

 

admin

GORMで既存のSQLite3のテーブルにアクセスする

他の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

SQLite3の非同期処理を同期化させる(Node.js)

便利なようで結構不便な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

 

Node.jsでSQLiteにアクセス

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~~~~以下省略

解決方法は、

https://stackoverflow.com/questions/53828891/dyld-library-not-loaded-usr-local-opt-icu4c-lib-libicui18n-62-dylib-error-run

から、

% 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のテーブルアクセスはできた。