[PYTHON] Ein Skript, das eine FlatBuffers-Binärdatei aus einer SQL-Datenbank erstellt

Als Serializer gibt es FlatBuffers, das von Google entwickelt wurde. Da Deserialisierung explosiv ist, scheint es, dass sie in Spielen usw. verwendet werden kann, aber es scheint, dass sie nicht beliebt ist. Ich dachte, dass einer der Gründe darin bestand, dass das Erstellen der Daten schwierig war. Deshalb habe ich ein Tool erstellt, das den Inhalt der SQL-Datenbank mit FlatBuffers so wie er ist serialisiert. Auf diese Weise können Sie die Struktur Ihrer Daten in einer SQL-Tabelle definieren.

Bei Social Games wird die Definition der Stammdaten normalerweise vom Serveringenieur vorgenommen, und der Client scheint den Inhalt als DB-Datei von JSON oder SQLite zu erhalten, die Clientseite wartet jedoch, bis die Konvertierungsunterstützung abgeschlossen ist. Wenn Sie dieses Tool verwenden, können Sie dem Client jedoch Daten erstellen, wenn die SQL-Datentabelle erstellt wird, und Sie können sofort auf Änderungen in der Tabelle reagieren.

Vorbereitung


Bereiten Sie eine Datenbank für Stammdaten vor

Das Tool setzt MySQL voraus. Erstellen Sie die gewünschte Tabelle. Wenn Sie ein Beispiel wünschen, verwenden Sie das offizielle MySQL sakila-db.

Installieren Sie die FlatBuffers-Tools

Es gibt ein Build-Tool namens flatc, das es zur Verfügung stellt. Für Mac ist "Brew Install Flat Buffers" in Ordnung. Super einfach. Geben Sie für alle Fälle flatc in die Konsole ein, um sicherzustellen, dass es sich in Ihrem Pfad befindet.

Installieren Sie Python

Installieren Sie es, da es das beliebte Python für die Konvertierung verwendet.

Erstellen Sie einen geeigneten Arbeitsordner

Dann legen Sie die folgenden Dateien.

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

#Spaltennamen und -typ abrufen
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')

Erstellen Sie eine Definitionsdatei für die Konvertierung

Erstellen Sie eine Datei, die die Verbindungsinformationen zu SQL und die Definition der Tabelle beschreibt, auf die verwiesen werden soll. Legen Sie es in den gleichen Ordner wie makefbs.py.

Setting.txt


mysql          (SQL-Auswahl)
localhost      (Verbindungszieladresse)
root           (Nutzername)
Pass-123       (Passwort)
sakila         (DB-Name)

TableName.txt


actor_info,SampleActorInfo         (DB-Tabellenname,Name der Ausgabeklasse)
customer_list,SampleCustomerList
film_list,SampleFilmList

Kopieren Sie die FlatBuffers-Datei zur Konvertierung

Erstellen Sie einen Ordner mit dem Namen flatbuffers und kopieren Sie die offiziell heruntergeladene Quelle für Python.

Konvertieren

Führen Sie python. / Makefbs.py in der Konsole aus. Es stellt eine Verbindung zu MySQL her, analysiert die angegebene Tabellenstruktur und generiert automatisch ein FlatBuffers-Schema. Führen Sie dann flatc aus, um die endgültigen Binärdaten zu erstellen. Eine Datendatei mit dem Namen db_master.dat und ein Arbeitsordner mit dem Namen conv werden erstellt. Dieses Tool dient zur Ausgabe einer Header-Datei für die Sprache C. Andere Sprachen können unterstützt werden, indem die Variable _flatparam in makefbs.py neu geschrieben wird. Im Ordner conv befindet sich eine Datei mit dem Namen "param_generated.h", die für die Deserialisierung verwendet wird.


Versuchen Sie es mit den erstellten Daten

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;
}

Im Beispiel wird eine Tabelle mit dem Namen player_info in eine std :: vector-Klasse mit dem Namen SampleActorInfoMaster gruppiert. Die Daten für jede Zeile sind als SampleActorInfoElement-Klasse definiert und können mit Get () abgerufen werden. Und es fühlt sich so an, als hätte param :: GetDbMaster mehrere Tabelleninformationen.

Die Erfassung selbst wird von std :: vector organisiert, daher denke ich, dass es einfach ist. Wenn sich die Tabellenstruktur ändert, dauert das Aktualisieren der Header-Datei einige Zeit, aber ich denke, es wäre praktisch, die SQL-Tabellenstruktur so wie sie ist in Daten zu konvertieren, was viel Zeit und Mühe spart. Da es sich um eine benutzerdefinierte Regel handelt, werden derzeit alle Spalten der Tabelle serialisiert. Sie müssen sich jedoch je nach Projekt mit makefbs.py herumschlagen, z. B. die Namensregel festlegen, damit Sie Spalten erstellen können, die nicht serialisiert sind. Ich denke es ist gut.

Recommended Posts

Ein Skript, das eine FlatBuffers-Binärdatei aus einer SQL-Datenbank erstellt
Python-Skript, das eine JSON-Datei aus einer CSV-Datei erstellt
Stellen Sie von Python aus eine Verbindung zur utf8mb4-Datenbank her
So erstellen Sie einen Klon aus Github
Skript zum Generieren eines Verzeichnisses aus einer JSON-Datei
Führen Sie ein Skript von Jupyter aus, um es zu verarbeiten
So erstellen Sie ein Repository aus Medien
Skript zum Erstellen einer Mac-Wörterbuchdatei
Bearbeiten Sie Excel in Python, um eine Pivot-Tabelle zu erstellen
So erstellen Sie ein Funktionsobjekt aus einer Zeichenfolge
Erstellen Sie eine neue Todoist-Aufgabe aus Python Script
[Python] Erstellen Sie eine Tabelle von Pandas DataFrame zu Postgres
Summe von 1 bis 10
SQL zu SQL
Verwendung der NUITKA-Utilities-Hinweis-Kompilierung zum einfachen Erstellen einer ausführbaren Datei aus einem Python-Skript
Ich habe Python-Code geschrieben, um ein Tabellen- (Ansichts-) Abhängigkeitsdiagramm (PlantUML) aus SQL zu erstellen
So erstellen Sie einen Artikel über die Befehlszeile
[Python3] Herstellen einer Verbindung zur Oracle-Datenbank und Ausführen von SQL [cx_Oracle]
Beschleunigen Sie die Verbindung von cx_Oracle zur autonomen Datenbank
So erstellen Sie ein einfaches TCP-Server / Client-Skript
Automatische Konvertierung von der MySQL Workbench-MWB-Datei in eine SQL-Datei
ODBC-Zugriff auf SQL Server von Linux mit Python