Pythonでクエリブラウザを作成中

自宅のUbuntuでSQLで何かをすることは滅多に無いけど、Windowsで言う所の、

「A5:SQL Mk-2(http://www.wind.sannet.ne.jp/m_matsu/developer/a5m2/)」とか
「CSE(Common SQL Environment)(http://www.hi-ho.ne.jp/tsumiki/)」とかの手軽に使えるものが欲しくなってきた。

Linuxで使えるものとしては、MySQLQueryBrowser(http://dev.mysql.com/doc/query-browser/en/index.html)や、Eclipseプラグインの「DBビューアプラグイン(http://www.ne.jp/asahi/zigen/home/plugin/dbviewer/about_jp.html)」があるけど、上記のように色々機能は付いてなくていいので単にSQLを実行して結果を返すような物があればいいかなと思うので

LinuxのGUI環境で動くものを「Python+Tkinter」で作ってみようと思う。(今のところMySQL専用)

で、今週は休日出社になってしまったので、今回は完全に作成途中のソース類を載せようと思います。

一応動く状態にはしているけど下記のイケテない所は後日対応しようと思います。

 

イケテない所(直さないといけない所)
・DBへの接続情報(ユーザID、パスワード、スキーマ)をソースに直書きしている。(ありえないので入力項目を設置又は、メニューに入れる)
・メニュー類が全くない。
・SELECT句で指定した順番で表示されない(これは痛い!軽くパニックを起こす。)

・日本語が文字化けする

・結果の表示に「セル(表)」(Excelみたいに表示するヤツ)tktableをまだ実装していない。
・エラー処理入ってません。
・その他色々足りてない。

色々落ち着いてきたらgithub(https://github.com/brokendish)に置こうと思います。

 

動かすために必要な物
sudo apt-get install python-tk
sudo apt-get install python-mysqldb

 

必要なソース(下記に掲載)
mysql_python_get_tbl_id.py
mysql_python_set_user_data.py
mysql_python_sql_setter.py
python+MySQL.py

使用方法
「python+MySQL.py」の以下の部分を自分の環境に合わせる(手で修正する)
schemaNm=”HoGeDB” ・・スキーマ名
userNm=”hoge”   ・・ユーザ名
passWd=”hoge”   ・・パスワード

 

実行
./python+MySQL.py

 

SQLを入力する

 

選択して「F1」又は「SQL実行」ボタンで実行

 

その他「データベース一覧」

 

その他「union all」

 

その他「項目一覧を表示」

 

mysql_python_get_tbl_id.py

#! /usr/bin/env python
# -*- coding: UTF-8 -*-
 
import MySQLdb as msl
import mysql_python_set_user_data as UsrData
 
"""
指定スキーマのテーブル一覧を取得
"""
class GetTableId:
 
	def __init__(self,UsrData):
		self.schemaNm=UsrData.schm
		self.userNm=UsrData.usr
		self.pwssWd=UsrData.pwd
 
	"""
	テーブルID取得
	"""
	def getTblId(self):
		connect=msl.connect(passwd=self.pwssWd, user=self.userNm, db=self.schemaNm)
		cur=connect.cursor()
		sql_str = 'select table_name from INFORMATION_SCHEMA.TABLES where table_schema=' + '"' + self.schemaNm + '"'
		print	sql_str
 
		cur.execute(sql_str)
		ret_all = cur.fetchall()
 
		for inrec in ret_all:
			#print inrec[0],inrec[1],inrec[3],inrec[4]
			print inrec[0]
 
		cur.close()
		connect.close()
 
		return ret_all
 
	"""
	カラム名取得
	"""
	def getColumns(self,tblid):
		connect=msl.connect(passwd=self.pwssWd, user=self.userNm, db=self.schemaNm)
		cur=connect.cursor()
		sql_str = 'select COLUMN_NAME from INFORMATION_SCHEMA.columns where table_schema=' + '"' + self.schemaNm + '"' + ' and TABLE_NAME = ' + '"' + tblid + '"' + ' order by ordinal_position'
 
		print	sql_str
 
		cur.execute(sql_str)
		ret_all = cur.fetchall()
 
		for inrec in ret_all:
			#print inrec[0],inrec[1],inrec[3],inrec[4]
			print inrec[0]
 
		cur.close()
		connect.close()
 
		return ret_all

 

 

mysql_python_set_user_data.py

#! /usr/bin/env python
# -*- coding: UTF-8 -*-
 
"""
ユーザの接続情報を保持する
"""
class UserData():
	def __init__(self,usr,pwd,schm):
		self.usr = usr
		self.pwd = pwd
		self.schm = schm

mysql_python_sql_setter.py

#! /usr/bin/env python
# -*- coding: UTF-8 -*-
 
import MySQLdb as msl
import mysql_python_set_user_data as UsrData
 
"""
SQLを発行する
"""
class SqlSetter:
 
	def __init__(self,UsrData):
		self.schemaNm=UsrData.schm
		self.userNm=UsrData.usr
		self.pwssWd=UsrData.pwd
 
	def setSql(self,sqlStr):
		ret_all=[]
		connect=msl.connect(passwd=self.pwssWd, user=self.userNm, db=self.schemaNm)
 
		print sqlStr			#SQLを標準出力(デバグ用)
 
		"""
		#カーソルを使って全体を取得する場合(メモリリークが起きることがあるみたい)
		#cur=connect.cursor()
		#結果を辞書形式(連想配列)で返す
		cur=connect.cursor(msl.cursors.DictCursor)
		cur.execute(sqlStr)		#SQL実行
		ret_all = cur.fetchall()	#フェッチオール(全部取得しておく)
		cur.close()			#カーソルを閉じる
		"""
 
		#クエリを使用して結果を取得
		connect.query(sqlStr)
		ret=connect.use_result()
		while(True):
			"""
			fetch_row(1,1):
				  0 -- tuples (default)
			          1 -- dictionaries, key=column or table.column if duplicated
				  2 -- dictionaries, key=table.column
			"""
			get_row=ret.fetch_row(1,1)
			if not get_row: break
			ret_all.insert(0,get_row[0])
 
		connect.close()			#接続を切る
 
		return ret_all
 
	"""
	ヘッタを付けてタブ区切りにする
	"""
	def getRetStruct(self,ret_str):
		cati = ""
		key_list=ret_str[0].viewkeys()				#辞書型のキー(項目名)を取得
		for head in key_list:
			cati = cati + head + "\t"			#タブ区切りで繋ぐ
		cati = cati + "\n"					#項目名を繋げ終わったら改行
 
		for strr in ret_str:					#データ内容を取得
			for get_key in key_list:
 
				if not isinstance(strr[get_key],str):
					strr[get_key]=str(strr[get_key])#文字型に変換
 
				cati = cati + strr[get_key] + "\t" 	#タブ区切りで繋ぐ
 
			cati = cati + "\n"				#文字列を連結
 
		return cati

python+MySQL.py

#! /usr/bin/env python
# -*- coding: UTF-8 -*-
 
import sys
import os
import Tkinter as Tk
import commands as com
import ScrolledText as St
import tkFileDialog as dlg
import tkMessageBox as msb
import mysql_python_set_user_data as UsrData
import mysql_python_get_tbl_id as UserTbl
import mysql_python_sql_setter as SndSql
 
root=Tk.Tk()
root.title("Python+MySQL Browser")
histfile="Python+MySQL_hist"	#ヒストリファイル
root.geometry("1200x800")	#ウィンドウサイズ
 
schemaNm="HoGeDB"
userNm="hoge"
passWd="hoge"
 
usrData = UsrData.UserData(userNm,passWd,schemaNm)
 
#-------------------------------------------------------------------------------
#ボタン1の処理	コマンド実行(イベントで呼ばれるようにするため(event)をつける)
#-------------------------------------------------------------------------------
def button1(event):
	cmd=inTex.get(Tk.SEL_FIRST, Tk.SEL_LAST).encode('utf-8')	#選択範囲の文字を渡す
	text.delete("1.0","end")					#1行目の0文字目から最後までを削除
 
	ret_str=SndSql.SqlSetter(usrData).setSql(cmd)			#SQLを発行してデータを取得
	dis_str=SndSql.SqlSetter(usrData).getRetStruct(ret_str)		#返却結果を成形する
	for disp_str in dis_str:
		text.insert("end",disp_str)				#テキストエリアに書き出す
 
	hist()								#履歴更新の処理
#-------------------------------------------------------------------------------
def button0():
	cmd=inTex.get(Tk.SEL_FIRST, Tk.SEL_LAST).encode('utf-8')	#選択範囲の文字を渡す
	text.delete("1.0","end")					#1行目の0文字目から最後までを削除
 
	ret_str=SndSql.SqlSetter(usrData).setSql(cmd)			#SQLを発行してデータを取得
	dis_str=SndSql.SqlSetter(usrData).getRetStruct(ret_str)		#返却結果を成形する
	for disp_str in dis_str:
		text.insert("end",disp_str)				#テキストエリアに書き出す
 
	hist()								#履歴更新の処理
#-------------------------------------------------------------------------------
#ボタン2の処理  表示エリアを削除
#-------------------------------------------------------------------------------
def button2():
	text.delete("1.0","end")	#1行目の0文字目から最後までを削除
#-------------------------------------------------------------------------------
#ボタン3の処理  コマンドエリアを削除
#-------------------------------------------------------------------------------
def button3():
	inTex.delete("1.0","end")	#1行目の0文字目から最後までを削除
#-------------------------------------------------------------------------------
#ボタン4の処理  履歴表示
#-------------------------------------------------------------------------------
def button4():
	if os.path.isfile(histfile):				#ファイルの存在確認
		for line in open(histfile,"r"):			#ファイルオープン&読込
			inTex.insert("end",line)        	#最後の行にインサートする
	else:
		msb.showinfo("履歴なし","今んとこ履歴は無いよ!")	#メッセージボックス表示
#-------------------------------------------------------------------------------
#ボタン5の処理  履歴削除
#-------------------------------------------------------------------------------
def hisdel():
	if os.path.isfile(histfile):					#ファイルの存在確認
		ret = msb.askquestion("履歴削除","履歴を削除しちゃうよ!")	#問い合わせダイアログ表示
		if ret == 'yes':
			os.remove(histfile)				#ファイル削除
	else:
		msb.showinfo("履歴なし","今んとこ履歴は無いよ!")		#メッセージボックス表示
#-------------------------------------------------------------------------------
#ボタン6の処理  結果保存
#-------------------------------------------------------------------------------
def saveas():
	#asksaveasfilename 保存場所を選択する。
	filename=dlg.asksaveasfilename()			#保存先選択のダイアログ表示
	if filename != '':
		f = open(filename,"w")				#ファイルオープン&書込
		f.write(text.get("1.0","end").encode('utf-8'))	#1行目の0文字目から最後まで
		f.close()					#ファイルクローズ
#-------------------------------------------------------------------------------
#履歴更新の処理
#-------------------------------------------------------------------------------
def hist():
	f = open(histfile,"a")					#ファイルオープン&書込
	f.write(inTex.get(Tk.SEL_FIRST, Tk.SEL_LAST) + "\n")	#選択範囲の文字を渡す
	f.close()
#-------------------------------------------------------------------------------
#テーブル一覧のリストをダブルクリックした時
#-------------------------------------------------------------------------------
def get_table(event):
	for gettblLine in lstbt.get('active'):
		print  gettblLine
	#カラム名取得
	lstbk.delete("0", "end")
	for colmnLine in UserTbl.GetTableId(usrData).getColumns(gettblLine):
		lstbk.insert("end",colmnLine)
#-------------------------------------------------------------------------------
#項目IDのリストをダブルクリックした時
#-------------------------------------------------------------------------------
def get_koumoku(event):
	print  lstbk.get('active')
	inTex.insert(Tk.INSERT,lstbk.get('active'))		#SQL入力エリアのカーソル位置にインサートする
#-------------------------------------------------------------------------------
#バインディング
root.bind("",button1)	#マウスの右クリックのイベントでbutton1を実行
root.bind("",button1)	#F1キーでbutton1を実行
 
#ラベル
lbl = Tk.Label(root, text="brokendish.org", font=('Times', '12'),anchor=Tk.E)
lbl.grid(row=0,column=1,rowspan=1,columnspan=5,sticky=Tk.E,padx=5, pady=5)
 
#テキストエリア
text=St.ScrolledText(root, font=('Times', '10'))
text.grid(row=1,column=0,rowspan=1,columnspan=5,sticky=Tk.W+Tk.E,padx=5, pady=5)
 
#ラベル(コメント)
msg="SQLを入力し、選択した部分を実行します。実行は「F1」、「右クリック」、「実行ボタン」"
lblm = Tk.Label(root, text=msg, font=('Times', '10'),anchor=Tk.W)
lblm.grid(row=2,column=0,rowspan=1,columnspan=5,sticky=Tk.W+Tk.E,padx=5, pady=5)
 
#ボタン6
btn6 = Tk.Button(root, text="保存", font=('Times', '12'),anchor=Tk.CENTER,width=10,command=saveas)
btn6.grid(row=2,column=1,rowspan=1,columnspan=5,sticky=Tk.E,padx=5, pady=5)
 
#テキストエリア(入力用)
inTex = St.ScrolledText(root, font=('Times', '12'))
inTex.grid(row=3,column=0,rowspan=1,columnspan=3,sticky=Tk.W + Tk.N + Tk.S,padx=5, pady=5)
 
#テーブル一覧
lstbt = Tk.Listbox(root,font=('Times', '12'),width=40)
for tblLine in UserTbl.GetTableId(usrData).getTblId():
	lstbt.insert("end",tblLine)		#テーブル一覧を取得
lstbt.grid(row=3,column=1,rowspan=1,columnspan=3,padx=5, pady=5,sticky=Tk.E + Tk.N + Tk.S)
 
#項目名一覧
lstbk = Tk.Listbox(root,font=('Times', '12'),width=30)
lstbk.grid(row=3,column=2,rowspan=1,columnspan=3,padx=5, pady=5,sticky=Tk.E + Tk.N + Tk.S)
 
#ボタン1
btn1 = Tk.Button(root, text="SQL実行", font=('Times', '12'),anchor=Tk.CENTER,command=button0)
btn1.grid(row=4,column=0,rowspan=1,columnspan=1,padx=5, pady=5)
 
#ボタン2
btn2 = Tk.Button(root, text="表示エリアを削除", font=('Times', '12'),anchor=Tk.CENTER,command=button2)
btn2.grid(row=4,column=1,rowspan=1,columnspan=1,padx=5, pady=5)
 
#ボタン3
btn3 = Tk.Button(root, text="SQLエリアを削除", font=('Times', '12'),anchor=Tk.CENTER,command=button3)
btn3.grid(row=4,column=2,rowspan=1,columnspan=1,padx=5, pady=5)
 
#ボタン4
btn4 = Tk.Button(root, text="履歴表示", font=('Times', '12'),anchor=Tk.CENTER,command=button4)
btn4.grid(row=4,column=3,rowspan=1,columnspan=1,padx=5, pady=5)
 
#ボタン5
btn5 = Tk.Button(root, text="履歴削除", font=('Times', '12'),anchor=Tk.CENTER,command=hisdel)
btn5.grid(row=4,column=4,rowspan=1,columnspan=1,padx=5, pady=5)
 
#リサイズに対応(横)
#root.grid_columnconfigure(0, weight = 4)
#リサイズに対応(縦)
#root.grid_rowconfigure(0, weight = 1)
 
#リサイズに対応
#root.grid_rowconfigure(0, weight = 1)
root.grid_columnconfigure(0, weight = 1)
root.grid_columnconfigure(1, weight = 1)
 
#root.grid_rowconfigure(1, weight = 2)
root.grid_columnconfigure(1, weight = 1)
 
#root.grid_rowconfigure(2, weight = 1)
root.grid_columnconfigure(2, weight = 1)
 
root.grid_rowconfigure(3, weight = 1)
root.grid_columnconfigure(0, weight = 3)
root.grid_columnconfigure(1, weight = 1)
root.grid_columnconfigure(2, weight = 1)
 
#root.grid_rowconfigure(4, weight = 2)
root.grid_columnconfigure(0, weight = 1)
root.grid_columnconfigure(1, weight = 1)
root.grid_columnconfigure(2, weight = 1)
root.grid_columnconfigure(3, weight = 1)
root.grid_columnconfigure(4, weight = 1)
 
#バインディング
lstbt.bind('', get_table)
lstbk.bind('', get_koumoku)
 
root.mainloop()
 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です