[PYTHON] Un script qui crée un binaire FlatBuffers à partir d'une base de données SQL

En tant que sérialiseur, il existe FlatBuffers développé par Google. La désérialisation étant explosive, il semble qu'elle puisse être utilisée dans les jeux, etc., mais il semble qu'elle ne soit pas populaire. Je pensais que l'une des raisons était qu'il était difficile de créer les données, j'ai donc créé un outil qui sérialise le contenu de la base de données SQL avec FlatBuffers tel quel. Cela vous permet de définir la structure de vos données dans une table SQL.

Dans le cas des jeux sociaux, la définition des données de base est généralement effectuée par l'ingénieur serveur, et le client semble recevoir le contenu sous forme de fichier DB de JSON ou SQLite, mais le côté client attend que le support de conversion soit terminé. Toutefois, si vous utilisez cet outil, vous pouvez fournir des données côté client lors de la création de la table de données SQL et vous pouvez répondre immédiatement aux modifications de la table.

Préparation


Préparer une base de données pour les données de base

L'outil suppose MySQL. Créez la table requise. Si vous voulez un échantillon, utilisez le MySQL officiel sakila-db.

Installer les outils FlatBuffers

Il existe un outil de construction appelé flatc qui le rend disponible. Pour mac, brew install flat buffers est OK. Super facile. Juste au cas où, tapez flatc sur la console pour vous assurer qu'il est dans votre chemin.

Installer python

Installez-le car il utilise Python populaire pour la conversion.

Créez un dossier de travail de manière appropriée

Ensuite, mettez les fichiers suivants.

makefbs.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
import os.path
import re
import shutil
import time
import sqlite3
import pymysql.cursors

_isSqliteDb = False

def GetConvName(_str):
    _convName = ''
    _names = re.split('[ _]', _str)
    for _name in _names:
        _convName = _convName + _name.capitalize()
    return _convName


class SchemaInfo:
    def __init__(self, _keyName, _keyType, _fbsKeyType):
        self.keyName = _keyName
        self.keyType = _keyType
        self.fbskeyName = GetConvName(_keyName)
        self.fbsKeyType = _fbsKeyType

#Obtenir le nom et le type de la colonne
def GetShemaInfo(_tableName, _conn):
    _keys = []
    _cur = _conn.cursor()
    if _isSqliteDb == True:
        # sqlite
        _order = "select sql from sqlite_master where type = 'table' and name = '" + _tableName + "'"
        _cur.execute(_order)
        _schema = _cur.fetchone()[0]
        _startPos = _schema.find(_tableName) + len(_tableName) + 1
        _schema = _schema[_startPos:-5]
        for _item in _schema.split(','):
            _itemTmp = _item.split(' ')
            if _itemTmp[1] == 'integer':
                _fbsKeyType = "int"
            elif _itemTmp[1] == 'real':
                _fbsKeyType = "float"
            elif _itemTmp[1] == 'numeric':
                _fbsKeyType = "int"
            else:
                _fbsKeyType = "string"
            _keys.append(SchemaInfo(_itemTmp[0], _itemTmp[1], _fbsKeyType))
    else:
        # mysql
        _order = "SHOW COLUMNS FROM " + _tableName 
        _cur.execute(_order)
        _schema = _cur.fetchall()
        for _item in _schema:
            _type = _item['Type']
            _isUnsigned = _type.find('unsigned') >= 0
            if _type.find('tinyint') >= 0:
                _fbsKeyType = "ubyte" if _isUnsigned else 'byte'
            elif _type.find('smallint') >= 0:
                _fbsKeyType = "ushort" if _isUnsigned else 'short'
            elif _type.find('bigint') >= 0:
                _fbsKeyType = "ulong" if _isUnsigned else 'long'
            elif _type.find('mediumint') >= 0 or _type.find('int') >= 0:
                _fbsKeyType = "uint" if _isUnsigned else 'int'
            elif _type.find('float') >= 0:
                _fbsKeyType = "float"
            elif _type.find('double') >= 0:
                _fbsKeyType = "double"
            else:
                _fbsKeyType = "string"
            _keys.append(SchemaInfo(_item['Field'], _item['Type'], _fbsKeyType))
    return _keys

def GetShemaInfoFromFbsKeyName(_keys, _name):
    for _item in _keys:
        if _item.fbskeyName == _name:
            return _item
    return None


def Create_Fbs(_conn, _list, _dbMasterName):
    _fd = open('param.fbs', 'w')
    _fd.write("// IDL file for our master's schema.\n")
    _fd.write("\n")
    _fd.write("namespace param;\n")
    _fd.write("\n")

    for _name in _list:
        Create_Master(_name, _fd, _conn)

    _fd.write("table " + _dbMasterName + "\n")
    _fd.write("{\n")
    for _name in _list:
        _fd.write(' ' + _name[1] + 'Master:' + _name[1] + "Master;\n")
    _fd.write("}\n")
    _fd.write("\n")
    _fd.write("root_type " + _dbMasterName + ";\n")
    _fd.close()


def Create_Master(_name, _fd, _conn):
    print("create " + _name[0])

    _keys = GetShemaInfo(_name[0], _conn)

    _fd.write("table " + _name[1] + "Element\n")
    _fd.write("{\n")
    for _key in _keys:
        _fd.write(" " + _key.fbskeyName + ":" + _key.fbsKeyType + ";\n")
    _fd.write("}\n\n")
    _fd.write("table " + _name[1] + "Master\n")
    _fd.write("{\n")
    _fd.write(" data:[" + _name[1] + "Element];\n")
    _fd.write("}\n\n\n")


def Create_Conv(_conn, _list):
    for _name in _list:
        _elementName = _name[1] + 'Element'
        _masterName = _name[1] + 'Master'
        _keyInfos = GetShemaInfo(_name[0], _conn)
        _startString = _elementName + "." + _elementName + "Start"
        _endString = _elementName + "." + _elementName + "End"

        _fd1 = open('conv' + _masterName + '.py', 'w')
        _fd1.write("import utility\n")
        if _isSqliteDb:
            _fd1.write("import sqlite3\n")
        else :
            _fd1.write("import pymysql.cursors\n")
        _fd1.write("from param import " + _elementName + "\n")
        _fd1.write("from param import " + _masterName + "\n")
        _fd1.write("\n")
        _fd1.write("class conv" + _masterName + "(object):\n")
        _fd1.write("\n")
        _fd1.write("    @classmethod\n")
        _fd1.write("    def Create(self, _conn, _builder):\n")
        _fd1.write("        _cmd = '" + _name[0] + "'\n")
        _fd1.write("        print(\"convert \" + _cmd)\n")
        _fd1.write("\n")
        _fd1.write("        _cur = _conn.cursor()\n")
        _fd1.write("        _cur.execute(\"SELECT * FROM \" + _cmd)\n")
        _fd1.write("        _list = _cur.fetchall()\n")
        _fd1.write("\n")
        _fd1.write("        _elements = []\n")
        _fd1.write("        for _row in _list:\n")

        _fd2 = open('param/' + _elementName + '.py', 'r')
        _strline = _fd2.readline()
        while _strline and _strline.find(_elementName + "Start(") < 0:
            _strline = _fd2.readline()

        _strline = _fd2.readline()
        _params = []
        while _strline and _strline.find(_elementName + "End(") < 0:
            _endPos = _strline.find("):")
            _strline = _elementName + '.' +_strline[4:(_endPos + 1)]
            _strline = _strline.replace('(builder', '(_builder')
            _params.append(_strline)
            _strline = _fd2.readline()

        for _param in _params:
            _fbsArgName = _param[(_param.rfind(', ') + 2):-1]
            _keyInfo = GetShemaInfoFromFbsKeyName(_keyInfos, _fbsArgName)
            _argName = "_" + _fbsArgName
            _writeText = "            " + _argName + " = "
            if _keyInfo.fbsKeyType == 'int' or _keyInfo.fbsKeyType == 'uint' or \
                _keyInfo.fbsKeyType == 'short' or _keyInfo.fbsKeyType == 'ushort' or \
                _keyInfo.fbsKeyType == 'long' or _keyInfo.fbsKeyType == 'ulong' or \
                _keyInfo.fbsKeyType == 'byte' or _keyInfo.fbsKeyType == 'ubyte':
                _writeText = _writeText + "utility.GetInt(_row['" + _keyInfo.keyName + "'])\n"
            elif _keyInfo.fbsKeyType == 'float':
                _writeText = _writeText + "utility.GetFloat(_row['" + _keyInfo.keyName + "'])\n"
            elif _keyInfo.fbsKeyType == 'double':
                _writeText = _writeText + "utility.GetDouble(_row['" + _keyInfo.keyName + "'])\n"
            else:
                _writeText = _writeText + "_builder.CreateString(utility.GetStr(_row['" + _keyInfo.keyName + "']))\n"
            _fd1.write(_writeText)

        _fd1.write("            " + _startString + "(_builder)\n")

        for _param in _params:
            _startPos = _param.rfind(', ') + 2
            _param = _param[:_startPos] + '_' + _param[_startPos:]
            _fd1.write("            " + _param + "\n")

        _fd1.write("            _elements.append(" + _endString + "(_builder))\n")
        _fd1.write("\n")
        _fd1.write("        " + _masterName + "." + _masterName + "StartDataVector(_builder, len(_elements))\n")
        _fd1.write("        for i in range(len(_elements)):\n")
        _fd1.write("            _builder.PrependUOffsetTRelative(_elements[i])\n")
        _fd1.write("        _data = _builder.EndVector(len(_elements))\n")
        _fd1.write("\n")
        _fd1.write("        " + _masterName + "." + _masterName + "Start(_builder)\n")
        _fd1.write("        " + _masterName + "." + _masterName + "AddData(_builder, _data)\n")
        _fd1.write("        return " + _masterName + "." + _masterName + "End(_builder)\n")

        _fd1.close()
        _fd2.close()


def Create_ConvBaseFile(_list, _dbParam, _outName, _dbMasterName):
    _fd1 = open('convAll.py', 'w')
    _fd1.write("#!/usr/bin/env python\n")
    _fd1.write("# -*- coding: utf-8 -*-\n")
    _fd1.write("import sys\n")
    _fd1.write("import os\n")
    _fd1.write("sys.path.append('../flatbuffers')\n")
    _fd1.write("import flatbuffers\n")
    if _isSqliteDb:
        _fd1.write("import sqlite3\n")
    else :
        _fd1.write("import pymysql.cursors\n")
    for _name in _list:
        _masterName = _name[1] + "Master"
        _fd1.write("import conv" + _masterName + "\n")
    _fd1.write("from param import DbMaster\n")
    _fd1.write("\n")
    _fd1.write("def Convert(_conn, _outName):\n")
    _fd1.write("    _builder = flatbuffers.Builder(0)\n")
    _fd1.write("\n")
    for _name in _list:
        _masterName = _name[1] + "Master"
        _fd1.write("    _" + _masterName + " = conv" + _masterName + ".conv" + _masterName + "().Create(_conn, _builder)\n")
    _fd1.write("\n")
    _DbMasterStr = _dbMasterName + "." + _dbMasterName
    _fd1.write("    " + _DbMasterStr + "Start(_builder)\n")
    for _name in _list:
        _masterName = _name[1] + "Master"
        _fd1.write("    " + _DbMasterStr + "Add" + _masterName + "(_builder, _" + _masterName + ")\n")
    _fd1.write("    _totalData = " + _DbMasterStr + "End(_builder)\n")
    _fd1.write("    _builder.Finish(_totalData)\n")
    _fd1.write("\n")
    _fd1.write("    _final_flatbuffer = _builder.Output()\n")
    _fd1.write("    _fd = open(_outName, 'wb')\n")
    _fd1.write("    _fd.write(_final_flatbuffer)\n")
    _fd1.write("    _fd.close()\n")
    _fd1.write("\n")
    _fd1.write("if __name__ == \"__main__\":\n")
    if _isSqliteDb:
        _fd1.write("    _conn = sqlite3.connect('app.db')\n")
        _fd1.write("    _conn.row_factory = sqlite3.Row\n")
    else:
        _fd1.write("    _conn = pymysql.connect(host='" + _dbParam[1] + "', user='" + _dbParam[2] + "', password='" + _dbParam[3] + "', db='" + _dbParam[4] + "', charset='utf8', cursorclass=pymysql.cursors.DictCursor)\n")
    _fd1.write("    Convert(_conn, '" + _outName + "')\n")
    _fd1.write("    _conn.close()\n")
    _fd1.close()


def Create_UtilityFile():
    _fd1 = open('utility.py', 'w')
    _fd1.write(\
    "def GetInt(_str):\n"\
    "    return 0 if _str is None or _str == 'None' else int(_str)\n"\
    "\n"\
    "def GetFloat(_str):\n"\
    "    return 0 if _str is None or _str == 'None' else float(_str)\n"\
    "\n"\
    "def GetDouble(_str):\n"\
    "    return 0 if _str is None or _str == 'None' else double(_str)\n"\
    "\n"\
    "def GetStr(_str):\n"\
    "    if isinstance(_str, unicode):\n"\
    "        return _str.encode('utf-8')\n"\
    "    if isinstance(_str, str):\n"\
    "        return _str\n"\
    "    return \"\" if _str is None else str(_str)\n"\
    )
    _fd1.close()


if __name__ == "__main__":
    _dbParam = []
    _isSqliteDb = False
    _settingName = 'Setting.txt'
    _inName = 'TableName.txt'
    _outName = 'db_master.dat'
    _dbMasterName = 'DbMaster'
    _flatparam = '-c'

    #for argv in sys.argv:

    if os.path.isfile(_settingName):
        _fd1 = open(_settingName, 'r')
        _dbParam = _fd1.read().splitlines()
        _fd1.close()

    _isSqliteDb = _dbParam[0] == "sqlite"

    _list = []
    _fd1 = open(_inName, 'r')
    _line = _fd1.readline().rstrip("\n")
    while _line:
        _list.append(_line.split(','))
        _line = _fd1.readline().rstrip("\n")

    if _isSqliteDb:
        _conn = sqlite3.connect(_dbParam[1])
        _conn.row_factory = sqlite3.Row
    else:
        _conn = pymysql.connect(host=_dbParam[1], user=_dbParam[2], password=_dbParam[3], db=_dbParam[4], charset='utf8', cursorclass=pymysql.cursors.DictCursor)

    # Create working directory
    shutil.rmtree("conv", True)
    time.sleep(0.1)
    os.makedirs("conv")
    os.chdir('conv')
    _fd = open('__init__.py', 'w')
    _fd.close()
    Create_UtilityFile()

    Create_Fbs(_conn, _list, _dbMasterName)
    os.system('flatc ' + _flatparam + ' -p param.fbs')

    Create_Conv(_conn, _list)
    Create_ConvBaseFile(_list, _dbParam, _outName, _dbMasterName)
    _conn.close()

    os.chdir('..')
    os.system('python conv/convAll.py')

Créer un fichier de définition pour la conversion

Créez un fichier qui décrit les informations de connexion à SQL et la définition de la table à laquelle se référer. Mettez-le dans le même dossier que makefbs.py.

Setting.txt


mysql          (Sélection SQL)
localhost      (Adresse de destination de la connexion)
root           (Nom d'utilisateur)
Pass-123       (mot de passe)
sakila         (Nom de la base de données)

TableName.txt


actor_info,SampleActorInfo         (Nom de la table de base de données,Nom de la classe de sortie)
customer_list,SampleCustomerList
film_list,SampleFilmList

Copiez le fichier FlatBuffers pour la conversion

Créez un dossier appelé flatbuffers et copiez le fichier [Source pour python] officiellement téléchargé (https://github.com/google/flatbuffers/tree/master/python/flatbuffers).

Convertir

Exécutez python. / Makefbs.py dans la console. Il se connecte à MySQL, analyse la structure de table spécifiée et génère automatiquement un schéma FlatBuffers. Exécutez ensuite flatc pour créer les données binaires finales. Un fichier de données appelé db_master.dat et un dossier de travail appelé conv sont créés. Cet outil est conçu pour générer un fichier d'en-tête pour le langage C. D'autres langages peuvent être pris en charge en réécrivant la variable _flatparam dans makefbs.py. Il y a un fichier appelé param_generated.h dans le dossier conv, qui sera utilisé pour la désérialisation.


Essayez d'utiliser les données créées

python


#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include "param_generated.h"

int main(int argc, const char * argv[])
{
	struct stat results;
	
	if (stat(argv[1], &results) != 0)
	{
		std::cout << "file not found\n";
		return -1;
	}

	char* buff = new char[results.st_size];
	std::ifstream file;
	file.open(argv[1], std::ios::in | std::ios::binary);
	if (file)
	{
		file.read(buff, results.st_size);
		auto data = param::GetDbMaster(buff);
		auto actorMaster = data->SampleActorInfoMaster()->data();
		auto num = actorMaster->size();
		for (int i = 0; i < num; i++)
		{
			auto data = actorMaster->Get(i);
			std::cout << data->ActorId() << " " << data->FirstName()->c_str() << " " << data->LastName()->c_str() << "\n";
		}
	}
	delete[] buff;

    return 0;
}

Dans l'exemple, une table appelée actor_info est regroupée dans une classe std :: vector appelée SampleActorInfoMaster. Les données de chaque ligne sont définies comme la classe SampleActorInfoElement et peuvent être obtenues avec Get (). Et il semble que param :: GetDbMaster a plusieurs informations de table.

L'acquisition elle-même est organisée par std :: vector, donc je pense que c'est facile. Lorsque la structure de la table change, la mise à jour du fichier d'en-tête prend du temps, mais je pense qu'il serait pratique de convertir la structure de la table SQL en données telle quelle, ce qui économise beaucoup de temps et d'efforts. Comme il est fait sur la prémisse de la coutume, toutes les colonnes de la table sont sérialisées à l'heure actuelle, mais avez-vous jouer avec makefbs.py en fonction de chaque projet, comme décider de la règle de dénomination afin que vous puissiez créer des colonnes qui ne sont pas sérialisées. Je pense que c'est bien.

Recommended Posts

Un script qui crée un binaire FlatBuffers à partir d'une base de données SQL
Script Python qui crée un fichier JSON à partir d'un fichier CSV
Connectez-vous à la base de données utf8mb4 à partir de python
Comment créer un clone depuis Github
Script pour générer un répertoire à partir d'un fichier json
Exécuter un script depuis Jupyter pour traiter
Comment créer un référentiel à partir d'un média
Script pour créer un fichier de dictionnaire Mac
Modifier Excel à partir de Python pour créer un tableau croisé dynamique
Comment créer un objet fonction à partir d'une chaîne
Créer une nouvelle tâche Todoist à partir d'un script Python
[python] Créer une table de pandas DataFrame vers postgres
Somme de 1 à 10
sql à sql
Comment utiliser NUITKA-Utilities hinted-compilation pour créer facilement un fichier exécutable à partir d'un script Python
J'ai écrit du code Python pour créer un diagramme de dépendance de table (vue) (PlantUML) à partir de SQL
Comment créer un article à partir de la ligne de commande
[Python3] Connexion à Oracle Database et exécution de SQL [cx_Oracle]
Accélération lors de la connexion de cx_Oracle à la base de données autonome
Comment créer un simple script serveur / client TCP
Conversion automatique du fichier MySQL Workbench mwb en fichier sql
Accès ODBC à SQL Server depuis Linux avec Python