961 lines
30 KiB
Python
Executable File
961 lines
30 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import os
|
|
import sys
|
|
|
|
# Workaround for MAC utf-8 encoding
|
|
if sys.platform == 'darwin':
|
|
os.environ['LC_ALL']='en_US.UTF-8'
|
|
reload(sys)
|
|
sys.setdefaultencoding("utf-8")
|
|
|
|
# Workaround for windows - DISABLED
|
|
#try: import lib.winunicode
|
|
#except ImportError: pass
|
|
#else: del lib.winunicode
|
|
|
|
import functions
|
|
import re
|
|
import apsw
|
|
import traceback
|
|
import json
|
|
import math
|
|
import random
|
|
|
|
pipedinput=not sys.stdin.isatty()
|
|
errorexit = True
|
|
nobuf = False
|
|
|
|
if pipedinput:
|
|
# If we get piped input use dummy readline
|
|
readline=lambda x:x
|
|
readline.remove_history_item=lambda x:x
|
|
readline.read_history_file=lambda x:x
|
|
readline.write_history_file=lambda x:x
|
|
readline.set_completer=lambda x:x
|
|
readline.add_history=lambda x:x
|
|
readline.parse_and_bind=lambda x:x
|
|
readline.set_completer_delims=lambda x:x
|
|
else:
|
|
# Workaround for absence of a real readline module in win32
|
|
import lib.reimport
|
|
if sys.platform == 'win32':
|
|
import pyreadline as readline
|
|
else:
|
|
import readline
|
|
|
|
import datetime
|
|
import time
|
|
import locale
|
|
import os
|
|
|
|
from lib.dsv import writer
|
|
import csv
|
|
|
|
try:
|
|
if pipedinput:
|
|
raise 'go to except'
|
|
import lib.colorama as colorama
|
|
from colorama import Fore, Back, Style
|
|
colnums = True
|
|
except:
|
|
colorama=lambda x:x
|
|
def dummyfunction():
|
|
pass
|
|
colorama.deinit = colorama.init = dummyfunction
|
|
Fore = Style = Back = dummyfunction
|
|
Fore.RED = Style.BRIGHT = Style.RESET_ALL = ''
|
|
colnums = False
|
|
pass
|
|
|
|
DELIM = Fore.RED+Style.BRIGHT+'|'+Style.RESET_ALL
|
|
|
|
class mtermoutput(csv.Dialect):
|
|
def __init__(self):
|
|
self.delimiter='|'
|
|
if not allquote:
|
|
self.quotechar='|'
|
|
self.quoting=csv.QUOTE_MINIMAL
|
|
else:
|
|
self.quotechar='"'
|
|
self.quoting=csv.QUOTE_NONNUMERIC
|
|
self.escapechar="\\"
|
|
self.lineterminator='\n'
|
|
|
|
def createConnection(db):
|
|
try:
|
|
if 'SQLITE_OPEN_URI' in apsw.__dict__:
|
|
connection = functions.Connection(db, flags=apsw.SQLITE_OPEN_READWRITE | apsw.SQLITE_OPEN_CREATE | apsw.SQLITE_OPEN_URI)
|
|
else:
|
|
connection = functions.Connection(db)
|
|
functions.register(connection)
|
|
connection.enableloadextension(True)
|
|
except Exception, e:
|
|
exitwitherror(e)
|
|
|
|
# Change TEMP store to where the mterm is run from
|
|
try:
|
|
connection.cursor().execute("PRAGMA temp_store_directory='.';PRAGMA page_size=16384;PRAGMA default_cache_size=3000;")
|
|
# if pipedinput:
|
|
except:
|
|
pass
|
|
|
|
functions.settings['stacktrace'] = True
|
|
|
|
return connection
|
|
|
|
def reloadfunctions():
|
|
global connection, automatic_reload, db
|
|
|
|
if not automatic_reload:
|
|
return
|
|
|
|
modified=lib.reimport.modified()
|
|
|
|
if len(modified)==0 or (modified==['__main__']):
|
|
return
|
|
|
|
tmp_settings=functions.settings
|
|
tmp_vars=functions.variables
|
|
connection.close()
|
|
try:
|
|
lib.reimport.reimport(*[x for x in modified if x != '__main__'])
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
lib.reimport.reimport('functionslocal')
|
|
except ValueError:
|
|
pass
|
|
|
|
connection = createConnection(db)
|
|
functions.settings=tmp_settings
|
|
functions.variables=tmp_vars
|
|
|
|
def raw_input_no_history(*args):
|
|
global pipedinput
|
|
|
|
if pipedinput:
|
|
try:
|
|
input = raw_input()
|
|
except EOFError:
|
|
connection.close()
|
|
exit(0)
|
|
return input
|
|
|
|
try:
|
|
input = raw_input(*args)
|
|
except:
|
|
return None
|
|
if input!='':
|
|
try:
|
|
readline.remove_history_item(readline.get_current_history_length()-1)
|
|
except:
|
|
pass
|
|
return input
|
|
|
|
def update_tablelist():
|
|
global alltables, alltablescompl, connection
|
|
alltables=[]
|
|
alltablescompl=[]
|
|
cursor = connection.cursor()
|
|
cexec=cursor.execute('PRAGMA database_list;')
|
|
for row in cexec:
|
|
cursor1 = connection.cursor()
|
|
if row[1]=='temp':
|
|
cexec1 = cursor1.execute("select name from sqlite_temp_master where type='table';")
|
|
else:
|
|
cexec1 = cursor1.execute("select name from "+row[1]+".sqlite_master where type='table';")
|
|
|
|
for row1 in cexec1:
|
|
tname=row1[0].lower().encode('ascii')
|
|
if row[1] in ('main', 'temp'):
|
|
alltables.append(tname)
|
|
alltablescompl.append(tname)
|
|
else:
|
|
dbtname=(row[1]+'.'+tname).lower().encode('ascii')
|
|
alltables.append(dbtname)
|
|
alltablescompl.append(dbtname)
|
|
if tname not in alltablescompl:
|
|
alltablescompl.append(tname)
|
|
cursor1.close()
|
|
cursor.close()
|
|
|
|
def get_table_cols(t):
|
|
global connection
|
|
|
|
if '.' in t:
|
|
ts=t.split('.')
|
|
dbname=ts[0]
|
|
tname='.'.join(ts[1:])
|
|
else:
|
|
dbname='main'
|
|
tname=t
|
|
cursor = connection.cursor()
|
|
if dbname=='main':
|
|
cexec=cursor.execute('pragma table_info('+str(tname)+')')
|
|
cols=[x[1] for x in cexec]
|
|
else:
|
|
cexec=cursor.execute('select * from '+str(tname))
|
|
cols=[x[0] for x in cursor.getdescriptionsafe()]
|
|
return cols
|
|
|
|
def sizeof_fmt(num, use_kibibyte=False):
|
|
base, infix = [(1000.,''),(1024.,'i')][use_kibibyte]
|
|
for x in ['','K%s'%infix,'M%s'%infix,'G%s'%infix]:
|
|
if num < base and num > -base:
|
|
if x == '':
|
|
return str(int(num))
|
|
else:
|
|
return "%3.1f%s" % (num, x)
|
|
num /= base
|
|
return "%3.1f %s" % (num, 'T%sB'%infix)
|
|
|
|
def approx_rowcount(t):
|
|
timer = time.time()
|
|
minrowid = list(connection.cursor().execute('select min(_rowid_) from ' + t, parse=False))[0][0]
|
|
maxrowid = list(connection.cursor().execute('select max(_rowid_) from ' + t, parse=False))[0][0]
|
|
if maxrowid is None or minrowid is None:
|
|
return 0
|
|
idrange = maxrowid - minrowid + 1
|
|
if (time.time() - timer) > 0.5:
|
|
return idrange
|
|
samplesize = min(int(math.sqrt(idrange)), 100)
|
|
if samplesize == 0:
|
|
return 0
|
|
step = idrange / samplesize
|
|
sample = range(random.randrange(0, step) + minrowid, maxrowid + 1, step)
|
|
samplesize = len(sample)
|
|
samplehits = 0
|
|
samplestep = 1
|
|
timer = time.time()
|
|
while sample != []:
|
|
samplehits += list(connection.cursor().execute(
|
|
'select count(*) from ' + t +
|
|
' where _rowid_ in (' + ','.join([str(x) for x in sample[:samplestep]]) + ');', parse=False))[0][0]
|
|
sample = sample[samplestep:]
|
|
samplestep *= 2
|
|
estimt = samplesize * (time.time() - timer) / (samplesize - len(sample))
|
|
if estimt > 0.5:
|
|
return idrange
|
|
return int(idrange * float(samplehits) / samplesize)
|
|
|
|
|
|
def update_cols_for_table(t):
|
|
global alltablescompl, colscompl, lastcols, connection, updated_tables
|
|
if t!='':
|
|
if t[-1]=='.':
|
|
t=t[0:-1]
|
|
if t[-2:]=='..':
|
|
t=t[0:-2]
|
|
|
|
if t in alltablescompl and t not in updated_tables:
|
|
try:
|
|
cols=get_table_cols(t)
|
|
updated_tables.add(t)
|
|
colscompl+= ['.'.join([ t, x ]) for x in cols]
|
|
colscompl+= [x for x in cols]
|
|
colscompl+=[t+'..']
|
|
except:
|
|
pass
|
|
try:
|
|
if '.' in t:
|
|
ts=t.split('.')
|
|
dbname=ts[0]
|
|
tname='.'.join(ts[1:])
|
|
else:
|
|
dbname='main'
|
|
tname=t
|
|
cursor = connection.cursor()
|
|
cexec=cursor.execute('select * from '+dbname+".sqlite_master where type='index' and tbl_name='"+str(tname)+"'")
|
|
icompl= [x[1] for x in cexec]
|
|
colscompl+= ['.'.join([ t, x ]) for x in icompl]
|
|
colscompl+= icompl
|
|
except:
|
|
pass
|
|
try:
|
|
colscompl=list(set(colscompl)-set(lastcols))
|
|
except:
|
|
pass
|
|
|
|
_update_cols_from_tables_last_text=''
|
|
def update_cols_from_tables_in_text(t):
|
|
global alltablescompl, _update_cols_from_tables_last_text
|
|
|
|
if t==_update_cols_from_tables_last_text:
|
|
return
|
|
|
|
_update_cols_from_tables_last_text=t
|
|
|
|
stablesreg='|'.join( (x.replace('$','\$').replace('.', '\.') for x in sorted(alltablescompl, key=len, reverse=True)) )
|
|
foundtables=re.findall(r'[^a-zA-Z0-9_$]('+stablesreg+r')[,.\s)]', t+u' ')
|
|
|
|
for i in foundtables:
|
|
update_cols_for_table(i)
|
|
|
|
|
|
def mcomplete(textin,state):
|
|
global number_of_kb_exceptions
|
|
number_of_kb_exceptions = 0
|
|
|
|
def normalizename(col):
|
|
if re.match(ur'\.*[\w_$\d.]+\s*$', col,re.UNICODE):
|
|
return col
|
|
else:
|
|
return "`"+col.lower()+"`"
|
|
|
|
def numberedlist(c):
|
|
maxcolchars=len(str(len(c)+1))
|
|
formatstring='{:'+'>'+str(maxcolchars)+'}'
|
|
o=[]
|
|
for num in xrange(len(c)):
|
|
o.append( formatstring.format(num+1)+'|'+c[num] )
|
|
return o
|
|
|
|
text=textin
|
|
|
|
#Complete \t to tabs
|
|
if text[-2:]=='\\t':
|
|
if state==0: return text[:-2]+'\t'
|
|
else: return
|
|
|
|
prefix=''
|
|
|
|
localtables=[]
|
|
completions=[]
|
|
|
|
linebuffer=readline.get_line_buffer()
|
|
|
|
beforecompl= linebuffer[0:readline.get_begidx()]
|
|
|
|
# Only complete '.xxx' completions when space chars exist before completion
|
|
if re.match(r'\s*$', beforecompl):
|
|
completions+=dotcompletions
|
|
# If at the start of the line, show all tables
|
|
if beforecompl=='' and text=='':
|
|
localtables=alltables[:]
|
|
|
|
# Check if all tables start with the same character
|
|
if localtables!=[]:
|
|
prefcharset=set( (x[0] for x in localtables) )
|
|
if len(prefcharset)==1:
|
|
localtables+=[' ']
|
|
completions=localtables
|
|
# If completion starts at a string boundary, complete from local dir
|
|
elif beforecompl!='' and beforecompl[-1] in ("'", '"'):
|
|
completions=os.listdir(os.getcwdu())
|
|
hits=[x for x in completions if x[:len(text)]==unicode(text)]
|
|
if state<len(hits):
|
|
return hits[state]
|
|
else: return
|
|
# Detect if in simplified 'from' or .schema
|
|
elif re.search(r'(?i)(from\s(?:\s*[\w\d._$]+(?:\s*,\s*))*(?:\s*[\w\d._$]+)?$)|(^\s*\.schema)|(^\s*\.t)|(^\s*\.tables)', beforecompl, re.DOTALL| re.UNICODE):
|
|
localtables=alltablescompl[:]
|
|
completions=localtables
|
|
else:
|
|
localtables=alltablescompl[:]
|
|
completions+=lastcols+colscompl
|
|
completions+=sqlandmtermstatements+allfuncs+localtables
|
|
|
|
hits= [x.lower() for x in completions if x.lower()[:len(text)]==unicode(text.lower())]
|
|
|
|
update_cols_from_tables_in_text(linebuffer)
|
|
|
|
if hits==[] and text.find('.')!=-1 and re.match(r'[\w\d._$]+', text):
|
|
tablename=re.match(r'(.+)\.', text).groups()[0].lower()
|
|
update_cols_for_table(tablename)
|
|
hits= [x.lower() for x in colscompl if x.lower()[:len(text)]==unicode(text.lower())]
|
|
|
|
|
|
# If completing something that looks like a table, complete only from cols
|
|
if hits==[] and text[-2:]!='..':
|
|
prepost=re.match(r'(.+\.)([^.]*)$', text)
|
|
if prepost:
|
|
prefix, text=prepost.groups()
|
|
hits= [x.lower() for x in lastcols+[y for y in colscompl if y.find('.')==-1] if x.lower()[:len(text)]==unicode(text.lower())]
|
|
# Complete table.number
|
|
if len(hits) == 0 and text.isdigit():
|
|
cols= get_table_cols(prefix[:-1])
|
|
colnum = int(text)
|
|
if 0 < colnum <= len(cols):
|
|
hits = [cols[colnum-1]]
|
|
elif colnum == 0:
|
|
hits = numberedlist(cols)
|
|
if state<len(hits):
|
|
return hits[state]
|
|
else: return
|
|
|
|
try:
|
|
# Complete from colnums
|
|
icol=int(text)
|
|
if len(hits)==0 and text.isdigit() and icol>=0:
|
|
# Show all tables when completing 0
|
|
if icol==0 and newcols!=[]:
|
|
if len(newcols)==1:
|
|
if state>0: return
|
|
return prefix+normalizename(newcols[0])
|
|
hits = numberedlist(newcols)
|
|
if state<len(hits):
|
|
return hits[state]
|
|
else: return
|
|
# Complete from last seen when completing for other number
|
|
if icol<=len(lastcols) and lastcols!=[] and state<1:
|
|
return prefix+normalizename(lastcols[icol-1])
|
|
except:
|
|
pass
|
|
|
|
if state<len(hits):
|
|
sqlstatem=set(sqlandmtermstatements)
|
|
altset=set(localtables)
|
|
|
|
if hits[state]=='..':
|
|
if text=='..' and lastcols!=[]:
|
|
return prefix+', '.join([normalizename(x) for x in lastcols])+' '
|
|
else:
|
|
return prefix+hits[state]
|
|
if hits[state] in sqlstatem:
|
|
return prefix+hits[state]
|
|
if hits[state] in colscompl:
|
|
if text[-2:]=='..':
|
|
tname=text[:-2]
|
|
try:
|
|
cols=get_table_cols(tname)
|
|
return prefix+', '.join(cols)+' '
|
|
except:
|
|
pass
|
|
if hits[state] in altset:
|
|
if text in altset:
|
|
update_cols_for_table(hits[state])
|
|
return prefix+hits[state]
|
|
else:
|
|
return prefix+normalizename(hits[state])
|
|
else:
|
|
return
|
|
|
|
def buildrawprinter(separator):
|
|
return writer(sys.stdout, dialect=mtermoutput(), delimiter=str(separator))
|
|
|
|
def schemaprint(cols):
|
|
global pipedinput
|
|
|
|
if pipedinput:
|
|
return
|
|
|
|
if cols!=[]:
|
|
sys.stdout.write(Style.BRIGHT+'--- '+Style.NORMAL+ Fore.RED+'['+Style.BRIGHT+'0'+Style.NORMAL+'|'+Style.RESET_ALL+Style.BRIGHT+'Column names '+'---'+Style.RESET_ALL+'\n')
|
|
colschars=0
|
|
i1=1
|
|
for i in cols:
|
|
colschars+=len(i)+len(str(i1))+3
|
|
i1+=1
|
|
if colschars<=80:
|
|
i1=1
|
|
for i in cols:
|
|
sys.stdout.write(Fore.RED+'['+Style.BRIGHT+str(i1)+Style.NORMAL+'|'+Style.RESET_ALL+i+' ')
|
|
i1+=1
|
|
sys.stdout.write('\n')
|
|
else:
|
|
totalchars=min(colschars/80 +1, 12) * 80
|
|
mincolchars=12
|
|
colschars=0
|
|
i1=1
|
|
for i in cols:
|
|
charspercolname=max((totalchars-colschars)/(len(cols)+1-i1)-5, mincolchars)
|
|
colschars+=min(len(i), charspercolname)+len(str(i1))+3
|
|
if len(i)>charspercolname and len(cols)>1:
|
|
i=i[0:charspercolname-1]+'..'
|
|
else:
|
|
i=i+' '
|
|
sys.stdout.write(Fore.RED+'['+Style.BRIGHT+str(i1)+Style.NORMAL+'|'+Style.RESET_ALL+i)
|
|
i1+=1
|
|
sys.stdout.write('\n')
|
|
|
|
def printrow(row):
|
|
global rawprinter, colnums
|
|
|
|
if not colnums:
|
|
rawprinter.writerow(row)
|
|
return
|
|
|
|
rowlen=len(row)
|
|
i1=1
|
|
for d in row:
|
|
if rowlen>3:
|
|
if i1==1:
|
|
sys.stdout.write(Fore.RED+Style.BRIGHT+'['+'1'+'|'+Style.RESET_ALL)
|
|
else:
|
|
sys.stdout.write(Fore.RED+'['+str(i1)+Style.BRIGHT+'|'+Style.RESET_ALL)
|
|
else:
|
|
if i1!=1:
|
|
sys.stdout.write(Fore.RED+Style.BRIGHT+'|'+Style.RESET_ALL)
|
|
if type(d) in (int, float, long):
|
|
d=str(d)
|
|
elif d is None:
|
|
d=Style.BRIGHT+'null'+Style.RESET_ALL
|
|
try:
|
|
sys.stdout.write(d)
|
|
except KeyboardInterrupt:
|
|
raise
|
|
except:
|
|
sys.stdout.write(d.encode('utf_8', 'replace'))
|
|
|
|
i1+=1
|
|
sys.stdout.write('\n')
|
|
|
|
def printterm(*args, **kwargs):
|
|
global pipedinput
|
|
|
|
msg=','.join([unicode(x) for x in args])
|
|
|
|
if not pipedinput:
|
|
print(msg)
|
|
|
|
def exitwitherror(*args):
|
|
msg=','.join([unicode(x) for x in args])
|
|
|
|
if errorexit:
|
|
print
|
|
sys.exit(msg)
|
|
else:
|
|
print(json.dumps({"error":msg}, separators=(',',':'), ensure_ascii=False).encode('utf_8', 'replace'))
|
|
print
|
|
sys.stdout.flush()
|
|
|
|
def process_args():
|
|
global connection, functions, errorexit, db, nobuf
|
|
|
|
args = sys.argv[1:]
|
|
|
|
# Continue on error when input is piped in
|
|
if len(args) >= 1 and pipedinput:
|
|
setargs = set(args)
|
|
if '-bailoff' in setargs or '-coe' in setargs:
|
|
try:
|
|
setargs.remove('-bailoff')
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
setargs.remove('-coe')
|
|
except KeyError:
|
|
pass
|
|
|
|
errorexit = False
|
|
|
|
if '-nobuf' in setargs:
|
|
setargs.remove('-nobuf')
|
|
nobuf = True
|
|
|
|
args = list(setargs)
|
|
|
|
if len(args) >= 1:
|
|
db = args[-1]
|
|
if db == "-q":
|
|
db = ':memory:'
|
|
|
|
connection = createConnection(db)
|
|
|
|
if db == '' or db == ':memory':
|
|
functions.variables.execdb = None
|
|
else:
|
|
functions.variables.execdb = str(os.path.abspath(os.path.expandvars(os.path.expanduser(os.path.normcase(db)))))
|
|
|
|
# Found query in args
|
|
if len(args)>2:
|
|
st = ' '.join(args[2:])
|
|
st = st.decode(output_encoding)
|
|
|
|
c = connection.cursor()
|
|
try:
|
|
for r in c.execute(st):
|
|
rawprinter.writerow(r)
|
|
c.close()
|
|
except KeyboardInterrupt:
|
|
sys.exit()
|
|
finally:
|
|
try:
|
|
c.close()
|
|
except:
|
|
pass
|
|
sys.exit()
|
|
|
|
VERSION='1.0'
|
|
mtermdetails="mTerm - version "+VERSION
|
|
intromessage="""Enter ".help" for instructions
|
|
Enter SQL statements terminated with a ";" """
|
|
|
|
helpmessage=""".functions Lists all functions
|
|
.help Show this message (also accepts '.h' )
|
|
.help FUNCTION Show FUNCTION's help page
|
|
.quit Exit this program
|
|
.schema TABLE Show the CREATE statements
|
|
.quote Toggle between normal quoting mode and quoting all mode
|
|
.beep Make a sound when a query finishes executing
|
|
.tables List names of tables (you can also use ".t" or double TAB)
|
|
.t TABLE Browse table
|
|
.explain Explain query plan
|
|
.colnums Toggle showing column numbers
|
|
.separator SEP Change separator to SEP. For tabs use 'tsv' or '\\t' as SEP
|
|
Separator is used only when NOT using colnums
|
|
.vacuum Vacuum DB using a temp file in current path
|
|
.queryplan query Displays the queryplan of the query
|
|
|
|
Use: FILE or CLIPBOARD function for importing data
|
|
OUTPUT or CLIPOUT function for exporting data"""
|
|
|
|
if 'HOME' not in os.environ: # Windows systems
|
|
if 'HOMEDRIVE' in os.environ and 'HOMEPATH' in os.environ:
|
|
os.environ['HOME'] = os.path.join(os.environ['HOMEDRIVE'], os.environ['HOMEPATH'])
|
|
else:
|
|
os.environ['HOME'] = "C:\\"
|
|
|
|
histfile = os.path.join(os.environ["HOME"], ".mterm")
|
|
|
|
automatic_reload=False
|
|
if not pipedinput:
|
|
try:
|
|
readline.read_history_file(histfile)
|
|
except IOError:
|
|
pass
|
|
import atexit
|
|
atexit.register(readline.write_history_file, histfile)
|
|
|
|
automatic_reload=True
|
|
readline.set_completer(mcomplete)
|
|
readline.parse_and_bind("tab: complete")
|
|
readline.set_completer_delims(' \t\n`!@#$^&*()=+[{]}|;:\'",<>?')
|
|
|
|
separator = "|"
|
|
allquote = False
|
|
beeping = False
|
|
db = ""
|
|
language, output_encoding = locale.getdefaultlocale()
|
|
|
|
if output_encoding==None:
|
|
output_encoding='UTF8'
|
|
|
|
functions.variables.flowname='main'
|
|
|
|
rawprinter=buildrawprinter(separator)
|
|
|
|
process_args()
|
|
|
|
sqlandmtermstatements=['select ', 'create ', 'where ', 'table ', 'group by ', 'drop ', 'order by ', 'index ', 'from ', 'alter ', 'limit ', 'delete ', '..',
|
|
"attach database '", 'detach database ', 'distinct', 'exists ']
|
|
dotcompletions=['.help ', '.colnums', '.schema ', '.functions ', '.tables', '.explain ', '.vacuum', '.queryplan ']
|
|
allfuncs=functions.functions['vtable'].keys()+functions.functions['row'].keys()+functions.functions['aggregate'].keys()
|
|
alltables=[]
|
|
alltablescompl=[]
|
|
updated_tables=set()
|
|
update_tablelist()
|
|
lastcols=[]
|
|
newcols=[]
|
|
colscompl=[]
|
|
|
|
#Intro Message
|
|
if not pipedinput:
|
|
print mtermdetails
|
|
print "running on Python: "+'.'.join([str(x) for x in sys.version_info[0:3]])+', APSW: '+apsw.apswversion()+', SQLite: '+apsw.sqlitelibversion(),
|
|
try:
|
|
sys.stdout.write(", madIS: "+functions.VERSION+'\n')
|
|
except:
|
|
print
|
|
print intromessage
|
|
|
|
number_of_kb_exceptions=0
|
|
while True:
|
|
statement = raw_input_no_history("mterm> ")
|
|
if statement==None:
|
|
number_of_kb_exceptions+=1
|
|
print
|
|
if number_of_kb_exceptions<2:
|
|
continue
|
|
else:
|
|
readline.write_history_file(histfile)
|
|
break
|
|
|
|
#Skip comments
|
|
if statement.startswith('--'):
|
|
continue
|
|
|
|
number_of_kb_exceptions=0
|
|
statement=statement.decode(output_encoding)
|
|
#scan for commands
|
|
iscommand=re.match("\s*\.(?P<command>\w+)\s*(?P<argument>([\w\.]*))(?P<rest>.*)$", statement)
|
|
validcommand=False
|
|
queryplan = False
|
|
|
|
if iscommand:
|
|
validcommand=True
|
|
command=iscommand.group('command')
|
|
argument=iscommand.group('argument')
|
|
rest=iscommand.group('rest')
|
|
origstatement=statement
|
|
statement=None
|
|
|
|
if command=='separator':
|
|
tmpseparator=separator
|
|
if argument=='csv':
|
|
separator = ","
|
|
elif argument in ('tsv','\\t','\t'):
|
|
separator = "\t"
|
|
else:
|
|
separator = argument
|
|
|
|
if separator == '':
|
|
colnums = True
|
|
separator = '|'
|
|
|
|
if separator!=tmpseparator:
|
|
colnums = False
|
|
rawprinter=buildrawprinter(separator)
|
|
|
|
elif command=='explain':
|
|
statement=re.sub("^\s*\.explain\s+", "explain query plan ", origstatement)
|
|
|
|
elif command=='queryplan':
|
|
try:
|
|
statement = re.match(r"\s*\.queryplan\s+(.+)", origstatement).groups()[0]
|
|
queryplan = True
|
|
except IndexError:
|
|
pass
|
|
|
|
elif command=='quote':
|
|
allquote^=True
|
|
if allquote:
|
|
printterm("Quoting output, uncoloured columns")
|
|
colnums=False
|
|
else:
|
|
printterm("Not quoting output, coloured columns")
|
|
colnums=True
|
|
rawprinter=buildrawprinter(separator)
|
|
|
|
elif command=='beep':
|
|
beeping^=True
|
|
if beeping:
|
|
printterm("Beeping enabled")
|
|
else:
|
|
printterm("Beeping disabled")
|
|
|
|
elif command=='colnums':
|
|
colnums^=True
|
|
if colnums:
|
|
printterm("Colnums enabled")
|
|
else:
|
|
printterm("Colnums disabled")
|
|
|
|
elif 'tables'.startswith(command):
|
|
update_tablelist()
|
|
argument=argument.rstrip('; ')
|
|
if not argument:
|
|
maxtlen = 0
|
|
for i in sorted(alltables):
|
|
maxtlen = max(maxtlen, len(i))
|
|
maxtlen += 1
|
|
|
|
for i in sorted(alltables):
|
|
l = ('{:<' + str(maxtlen) + '}').format(i)
|
|
|
|
try:
|
|
l += DELIM + " cols:{:<4}".format(str(len(get_table_cols(i))))
|
|
except KeyboardInterrupt:
|
|
print
|
|
break
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
l += DELIM + " ~rows:" + sizeof_fmt(approx_rowcount(i))
|
|
except KeyboardInterrupt:
|
|
print
|
|
break
|
|
except:
|
|
pass
|
|
|
|
printterm(l)
|
|
else:
|
|
statement = 'select * from '+argument+' limit 2;'
|
|
|
|
elif 'select'.startswith(command):
|
|
update_tablelist()
|
|
argument = argument.rstrip('; ')
|
|
if not argument:
|
|
for i in sorted(alltables):
|
|
printterm(i)
|
|
else:
|
|
statement = 'select * from '+argument + ';'
|
|
|
|
elif command=='vacuum':
|
|
statement="PRAGMA temp_store_directory = '.';VACUUM;PRAGMA temp_store_directory = '';"
|
|
|
|
elif command=='schema':
|
|
if not argument:
|
|
statement="select sql from (select * from sqlite_master union all select * from sqlite_temp_master) where sql is not null;"
|
|
else:
|
|
argument=argument.rstrip('; ')
|
|
update_tablelist()
|
|
if argument not in alltables:
|
|
printterm("No table found")
|
|
else:
|
|
db='main'
|
|
if '.' in argument:
|
|
sa=argument.split('.')
|
|
db=sa[0]
|
|
argument=''.join(sa[1:])
|
|
statement="select sql from (select * from "+db+".sqlite_master union all select * from sqlite_temp_master) where tbl_name like '%s' and sql is not null;" %(argument)
|
|
|
|
elif "quit".startswith(command):
|
|
connection.close()
|
|
exit(0)
|
|
|
|
elif command=="functions":
|
|
for ftype in functions.functions:
|
|
for f in functions.functions[ftype]:
|
|
printterm(f+' :'+ftype)
|
|
|
|
elif "help".startswith(command):
|
|
if not argument:
|
|
printterm(helpmessage)
|
|
else:
|
|
for i in functions.functions:
|
|
if argument in functions.functions[i]:
|
|
printterm("Function "+ argument + ":")
|
|
printterm(functions.mstr(functions.functions[i][argument].__doc__))
|
|
|
|
elif command=="autoreload":
|
|
automatic_reload=automatic_reload ^ True
|
|
printterm("Automatic reload is now: " + str(automatic_reload))
|
|
|
|
else:
|
|
validcommand=False
|
|
printterm("""unknown command. Enter ".help" for help""")
|
|
|
|
if validcommand:
|
|
histstatement='.'+command+' '+argument+rest
|
|
try:
|
|
readline.add_history(histstatement.encode('utf-8'))
|
|
except:
|
|
pass
|
|
|
|
if statement:
|
|
histstatement=statement
|
|
while not apsw.complete(statement):
|
|
more = raw_input_no_history(' ..> ')
|
|
if more==None:
|
|
statement=None
|
|
break
|
|
more=more.decode(output_encoding)
|
|
statement = statement + '\n'.decode(output_encoding) + more
|
|
histstatement=histstatement+' '+more
|
|
|
|
reloadfunctions()
|
|
number_of_kb_exceptions=0
|
|
if not statement:
|
|
printterm()
|
|
continue
|
|
try:
|
|
if not validcommand:
|
|
readline.add_history(histstatement.encode('utf-8'))
|
|
except:
|
|
pass
|
|
|
|
before = datetime.datetime.now()
|
|
try:
|
|
if queryplan:
|
|
cexec = connection.queryplan(statement)
|
|
desc = cexec.next()
|
|
else:
|
|
cursor = connection.cursor()
|
|
cexec = cursor.execute(statement)
|
|
try:
|
|
desc = cursor.getdescriptionsafe()
|
|
lastcols[0:len(desc)] = [x for x, y in desc]
|
|
except apsw.ExecutionCompleteError, e:
|
|
desc = []
|
|
|
|
newcols=[x for x,y in desc]
|
|
|
|
colorama.init()
|
|
rownum=0
|
|
|
|
if not pipedinput:
|
|
for row in cexec:
|
|
printrow(row)
|
|
rownum+=1
|
|
else:
|
|
print(json.dumps({"schema":desc}, separators=(',',':'), ensure_ascii=False).encode('utf_8', 'replace'))
|
|
for row in cexec:
|
|
print(json.dumps(row, separators=(',',':'), ensure_ascii=False).encode('utf_8', 'replace'))
|
|
if nobuf:
|
|
sys.stdout.flush()
|
|
print
|
|
sys.stdout.flush()
|
|
|
|
if not queryplan:
|
|
cursor.close()
|
|
|
|
after=datetime.datetime.now()
|
|
tmdiff=after-before
|
|
|
|
schemaprint(newcols)
|
|
if not pipedinput:
|
|
if rownum==0:
|
|
printterm( "Query executed in %s min. %s sec %s msec." %((int(tmdiff.days)*24*60+(int(tmdiff.seconds)/60),(int(tmdiff.seconds)%60),(int(tmdiff.microseconds)/1000))) )
|
|
else:
|
|
print "Query executed and displayed %s"%(rownum),
|
|
if rownum==1: print "row",
|
|
else: print "rows",
|
|
print "in %s min. %s sec %s msec." %((int(tmdiff.days)*24*60+(int(tmdiff.seconds)/60),(int(tmdiff.seconds)%60),(int(tmdiff.microseconds)/1000)))
|
|
if beeping:
|
|
printterm('\a\a')
|
|
|
|
colscompl=[]
|
|
updated_tables=set()
|
|
|
|
#Autoupdate in case of schema change
|
|
if re.search(r'(?i)(create|attach|drop)', statement):
|
|
update_tablelist()
|
|
|
|
except KeyboardInterrupt:
|
|
print
|
|
schemaprint(newcols)
|
|
printterm("KeyboardInterrupt exception: Query execution stopped", exit=True)
|
|
continue
|
|
except (apsw.SQLError, apsw.ConstraintError , functions.MadisError), e:
|
|
emsg=unicode(e)
|
|
if pipedinput:
|
|
exitwitherror(functions.mstr(emsg))
|
|
else:
|
|
try:
|
|
if u'Error:' in emsg:
|
|
emsgsplit=emsg.split(u':')
|
|
print Fore.RED+Style.BRIGHT+ emsgsplit[0] +u':'+Style.RESET_ALL+ u':'.join(emsgsplit[1:])
|
|
else:
|
|
print e
|
|
except:
|
|
print e
|
|
|
|
continue
|
|
except Exception, e:
|
|
trlines = []
|
|
|
|
for i in reversed(traceback.format_exc(limit=sys.getrecursionlimit()).splitlines()):
|
|
trlines.append(i)
|
|
if i.strip().startswith('File'):
|
|
break
|
|
|
|
msg=Fore.RED+Style.BRIGHT+"Unknown error:" + Style.RESET_ALL + "\nTraceback is:\n" + '\n'.join(reversed(trlines))
|
|
if pipedinput:
|
|
exitwitherror(functions.mstr(msg))
|
|
else:
|
|
print msg
|
|
|
|
finally:
|
|
colorama.deinit()
|
|
try:
|
|
cursor.close()
|
|
except:
|
|
#print "Not proper clean-up"
|
|
pass
|
|
|