Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/databases/sql_db.py
8814 views
1
"""
2
Relational (sqlite) Databases Module.
3
4
INFO:
5
6
This module implements classes (SQLDatabase and SQLQuery (pythonic
7
implementation for the user with little or no knowledge of sqlite))
8
that wrap the basic functionality of sqlite.
9
10
Databases are constructed via a triple indexed dictionary called a
11
skeleton. A skeleton should be constructed to fit the following format:
12
13
- skeleton -- a triple-indexed dictionary
14
15
- outer key -- table name
16
17
- inner key -- column name
18
19
- inner inner key -- one of the following:
20
21
- primary_key -- boolean, whether column has been set as
22
primary key
23
- index -- boolean, whether column has been set as index
24
- unique -- boolean, whether column has been set as unique
25
- sql -- one of 'TEXT', 'BOOLEAN', 'INTEGER', 'REAL', or
26
other user defined type
27
28
An example skeleton of a database with one table, that table with one
29
column::
30
31
{'table1':{'col1':{'primary_key':False, 'index':True, 'sql':'REAL'}}}
32
33
SQLDatabases can also be constructed via the add, drop, and commit
34
functions. The vacuum function is also useful for restoring hard disk space
35
after a database has shrunk in size.
36
37
A SQLQuery can be constructed by providing a query_dict, which is a
38
dictionary with the following sample format::
39
40
{'table_name': 'tblname', 'display_cols': ['col1', 'col2', 'col3'], 'expression':[col, operator, value]}
41
42
Finally a SQLQuery also allows the user to directly input the query
43
string for a database, and also supports the '?' syntax by allowing an
44
argument for a tuple of parameters to query.
45
46
For full details, please see the tutorial. sage.graphs.graph_database.py
47
is an example of implementing a database class in Sage using this
48
interface.
49
50
AUTHORS:
51
-- R. Andrew Ohana (2011-07-16): refactored and rewrote most of the code;
52
merged the Generic classes into the
53
non-Generic versions; changed the
54
skeleton format to include a boolean
55
indicating whether the column stores
56
unique keys; changed the index names so
57
as to avoid potential ambiguity
58
-- Emily A. Kirkman (2008-09-20): added functionality to generate plots and
59
reformat output in show
60
-- Emily A. Kirkman and Robert L. Miller (2007-06-17): initial version
61
"""
62
# FUTURE TODOs (Ignore for now):
63
# - order by clause in query strings
64
# - delete from query containing joins
65
# - add data by column
66
# - wrap sqlalchemy
67
# - create query interface (with interact)
68
# - allow kwds arguments to SQLQuery (like GraphQuery)
69
70
#*****************************************************************************
71
# Copyright (C) 2011 R. Andrew Ohana <[email protected]>
72
# Copyright (C) 2007 Emily A. Kirkman
73
# Robert L. Miller
74
#
75
# Distributed under the terms of the GNU General Public License (GPL)
76
# as published by the Free Software Foundation; either version 2 of
77
# the License, or (at your option) any later version.
78
# http://www.gnu.org/licenses/
79
#*****************************************************************************
80
import sqlite3 as sqlite
81
import os
82
import re
83
from sage.misc.all import tmp_filename
84
from sage.structure.sage_object import SageObject
85
86
sqlite_keywords = ['ABORT','ACTION','ADD','AFTER','ALL','ALTER','ANALYZE',
87
'AND','AS','ASC','ATTACH','AUTOINCREMENT','BEFORE','BEGIN','BETWEEN','BY',
88
'CASCADE','CASE','CAST','CHECK','COLLATE','COLUMN','COMMIT','CONFLICT',
89
'CONSTRAINT','CREATE','CROSS','CURRENT_DATE','CURRENT_TIME',
90
'CURRENT_TIMESTAMP','DATABASE','DEFAULT','DEFERRABLE','DEFERRED','DELETE',
91
'DESC','DETACH','DISTINCT','DROP','EACH','ELSE','END','ESCAPE','EXCEPT',
92
'EXCLUSIVE','EXISTS','EXPLAIN','FAIL','FOR','FOREIGN','FROM','FULL',
93
'GLOB','GROUP','HAVING','IF','IGNORE','IMMEDIATE','IN','INDEX','INDEXED',
94
'INITIALLY','INNER','INSERT','INSTEAD','INTERSECT','INTO','IS','ISNULL',
95
'JOIN','KEY','LEFT','LIKE','LIMIT','MATCH','NATURAL','NO','NOT','NOTNULL',
96
'NULL','OF','OFFSET','ON','OR','ORDER','OUTER','PLAN','PRAGMA','PRIMARY',
97
'QUERY','RAISE','REFERENCES','REGEXP','REINDEX','RELEASE','RENAME',
98
'REPLACE','RESTRICT','RIGHT','ROLLBACK','ROW','SAVEPOINT','SELECT','SET',
99
'TABLE','TEMP','TEMPORARY','THEN','TO','TRANSACTION','TRIGGER','UNION',
100
'UNIQUE','UPDATE','USING','VACUUM','VALUES','VIEW','VIRTUAL','WHEN',
101
'WHERE']
102
103
def regexp(expr, item):
104
"""
105
Function to define regular expressions in pysqlite.
106
Returns ``True`` if parameter ``item`` matches the regular expression
107
parameter ``expr``.
108
Returns ``False`` otherwise (i.e.: no match).
109
110
REFERENCES:
111
112
.. [GH2005] Gerhard Haring. [Online] Available:
113
http://lists.initd.org/pipermail/pysqlite/2005-November/000253.html
114
115
EXAMPLES::
116
117
sage: from sage.databases.sql_db import regexp
118
sage: regexp('.s.*','cs')
119
True
120
sage: regexp('.s.*','ccs')
121
False
122
sage: regexp('.s.*','cscccc')
123
True
124
"""
125
r = re.compile(expr)
126
return r.match(item) is not None
127
128
def verify_type(type):
129
"""
130
Verify that the specified ``type`` is one of the allowed strings.
131
132
EXAMPLES::
133
134
sage: from sage.databases.sql_db import verify_type
135
sage: verify_type('INT')
136
True
137
sage: verify_type('int')
138
True
139
sage: verify_type('float')
140
Traceback (most recent call last):
141
...
142
TypeError: float is not a legal type.
143
144
"""
145
types = ['INTEGER','INT','BOOLEAN','REAL','TEXT','BOOL','BLOB','NOTYPE']
146
if type.upper() not in types:
147
raise TypeError('%s is not a legal type.'%type)
148
return True
149
150
def verify_column(col_dict):
151
"""
152
Verify that ``col_dict`` is in proper format, and return a dict with
153
default values filled in. Proper format::
154
155
{'primary_key':False, 'index':False, 'unique': False, 'sql':'REAL'}
156
157
EXAMPLES::
158
159
sage: from sage.databases.sql_db import verify_column
160
sage: col = {'sql':'BOOLEAN'}
161
sage: verify_column(col)
162
{'index': False, 'unique': False, 'primary_key': False,
163
'sql': 'BOOLEAN'}
164
sage: col = {'primary_key':True, 'sql':'INTEGER'}
165
sage: verify_column(col)
166
{'index': True, 'unique': True, 'primary_key': True,
167
'sql': 'INTEGER'}
168
sage: verify_column({})
169
Traceback (most recent call last):
170
...
171
ValueError: SQL type must be declared, e.g. {'sql':'REAL'}.
172
"""
173
d = {}
174
d['primary_key'] = col_dict.get('primary_key', False)
175
d['index'] = col_dict.get('index', False) or d['primary_key']
176
d['unique'] = col_dict.get('unique', False) or d['primary_key']
177
if 'sql' not in col_dict:
178
raise ValueError("SQL type must be declared, e.g. {'sql':'REAL'}.")
179
if verify_type(col_dict['sql']):
180
d['sql'] = col_dict['sql']
181
return d
182
183
def verify_operator(operator):
184
"""
185
Checks that ``operator`` is one of the allowed strings.
186
Legal operators include the following strings:
187
188
- '='
189
- '<='
190
- '>='
191
- '<'
192
- '>'
193
- '<>'
194
- 'like'
195
- 'regexp'
196
- 'is null'
197
- 'is not null'
198
199
EXAMPLES::
200
201
sage: from sage.databases.sql_db import verify_operator
202
sage: verify_operator('<=')
203
True
204
sage: verify_operator('regexp')
205
True
206
sage: verify_operator('is null')
207
True
208
sage: verify_operator('not_an_operator')
209
Traceback (most recent call last):
210
...
211
TypeError: not_an_operator is not a legal operator.
212
"""
213
binaries = ['=','<=','>=','like','<','>','<>','regexp']
214
unaries = ['is null','is not null']
215
if operator not in binaries and operator not in unaries:
216
raise TypeError('%s is not a legal operator.'%operator)
217
return True
218
219
def construct_skeleton(database):
220
"""
221
Constructs a database skeleton from the sql data. The skeleton data
222
structure is a triple indexed dictionary of the following format:
223
224
- skeleton -- a triple-indexed dictionary
225
226
- outer key -- table name
227
228
- inner key -- column name
229
230
- inner inner key -- one of the following:
231
232
- ``primary_key`` -- boolean, whether column has been set
233
as primary key
234
- ``index`` -- boolean, whether column has been set as
235
index
236
- ``unique`` -- boolean, whether column has been set as
237
unique
238
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
239
``'INTEGER'``, ``'REAL'``, or other user defined type
240
241
An example skeleton of a database with one table, that table with one
242
column::
243
244
{'table1':{'col1':{'primary_key':False, 'unique': True, 'index':True, 'sql':'REAL'}}}
245
246
EXAMPLES::
247
248
sage: G = SQLDatabase(GraphDatabase().__dblocation__, False)
249
sage: from sage.databases.sql_db import construct_skeleton
250
sage: construct_skeleton(G).keys()
251
[u'aut_grp', u'degrees', u'spectrum', u'misc', u'graph_data']
252
"""
253
skeleton = {}
254
cur = database.__connection__.cursor()
255
exe = cur.execute("SELECT name FROM sqlite_master WHERE TYPE='table'")
256
from sage.misc.misc import SAGE_SHARE
257
for table in exe.fetchall():
258
skeleton[table[0]] = {}
259
exe1 = cur.execute("PRAGMA table_info(%s)"%table[0])
260
for col in exe1.fetchall():
261
if not col[2]:
262
typ = u'NOTYPE'
263
else:
264
typ = col[2]
265
skeleton[table[0]][col[1]] = {'sql':typ, \
266
'primary_key':(col[5]!=0), 'index':(col[5]!=0), 'unique': False}
267
exe2 = cur.execute("PRAGMA index_list(%s)"%table[0])
268
for col in exe2.fetchall():
269
if col[1].find('sqlite') == -1:
270
if database.__dblocation__ == \
271
os.path.join(SAGE_SHARE,'graphs','graphs.db'):
272
name = col[1]
273
else:
274
name = col[1][len(table[0])+3:]
275
skeleton[table[0]][name]['index'] = True
276
skeleton[table[0]][name]['unique'] = bool(col[2])
277
else:
278
name = cur.execute("PRAGMA index_info(%s)"%col[1])
279
name = name.fetchone()[2]
280
skeleton[table[0]][name]['unique'] = bool(col[2])
281
return skeleton
282
283
p = 0
284
def _create_print_table(cur, col_titles, **kwds):
285
"""
286
Creates a nice printable table from the cursor given with the given
287
column titles.
288
289
KEYWORDS:
290
291
- ``max_field_size`` -- how wide each field can be
292
- ``format_cols`` -- a dictionary that allows the user to specify the
293
format of a column's output by supplying a function. The format of
294
the dictionary is::
295
296
{'column_name':(lambda x: format_function(x))}
297
298
- ``plot_cols`` -- a dictionary that allows the user to specify that a
299
plot should be drawn by the object generated by a data slice. Note
300
that plot kwds are permitted. The dictionary format is::
301
302
{'column_name':((lambda x: plot_function(x)),**kwds)}
303
304
- ``relabel_cols`` -- a dictionary to specify a relabeling of column
305
headers. The dictionary format is::
306
307
{'table_name':{'old_col_name':'new_col_name'}}
308
309
- ``id_col`` -- reference to a column that can be used as an object
310
identifier for each row
311
312
- ``html_table`` -- boolean that if True creates an html table instead of
313
a print table. Always set to True in the notebook.
314
315
EXAMPLES::
316
317
sage: DB = SQLDatabase()
318
sage: DB.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
319
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
320
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
321
sage: from sage.databases.sql_db import _create_print_table
322
sage: cur = r.__database__.__connection__.cursor()
323
sage: exe = cur.execute(r.__query_string__, r.__param_tuple__)
324
sage: _create_print_table(cur, [des[0] for des in cur.description])
325
'a1 \n--------------------\n0 \n1 \n1 '
326
"""
327
fcol_index = []
328
pcol_index = []
329
330
if 'format_cols' in kwds:
331
fcol_map = []
332
for col in kwds['format_cols']:
333
fcol_map.append(kwds['format_cols'][col])
334
fcol_index.append(col_titles.index(col))
335
if 'plot_cols' in kwds:
336
pcol_map = []
337
for col in kwds['plot_cols']:
338
pcol_map.append(kwds['plot_cols'][col])
339
pcol_index.append(col_titles.index(col))
340
341
max_field_size = kwds['max_field_size'] if 'max_field_size' in kwds \
342
else 20
343
id_col_index = col_titles.index(kwds['id_col']) if 'id_col' in kwds \
344
else None
345
346
if 'relabel_cols' in kwds:
347
relabel_cols = kwds['relabel_cols']
348
for i in range(len(col_titles)):
349
try:
350
col_titles[i] = relabel_cols[col_titles[i]]
351
except KeyError:
352
continue
353
global p
354
p = 0
355
def row_str(row, html):
356
f = 0
357
global p
358
cur_str = []
359
for index in range(len(col_titles)):
360
if index in pcol_index:
361
if html:
362
plot = pcol_map[p%len(pcol_map)](row[index])
363
plot.save('%d.png'%p, figsize=[1,1])
364
field_val = ' <td bgcolor=white align=center> ' \
365
+ '%s <br> <img src="cell://%d.png"> '%(row[index],p) \
366
+ '</td>\n'
367
p += 1
368
else:
369
raise NotImplementedError('Cannot display plot on ' \
370
+ 'command line.')
371
else:
372
if index in fcol_index:
373
if id_col_index is None:
374
field_val = fcol_map[f](row[index])
375
else:
376
field_val = fcol_map[f](row[index], row[id_col_index])
377
f += 1
378
else:
379
field_val = row[index]
380
if html:
381
field_val = ' <td bgcolor=white align=center> ' \
382
+ str(field_val) + ' </td>\n'
383
else:
384
field_val = str(field_val).ljust(max_field_size)
385
cur_str.append(field_val)
386
return ' '.join(cur_str)
387
388
from sage.server.support import EMBEDDED_MODE
389
if EMBEDDED_MODE or ('html_table' in kwds and kwds['html_table']):
390
# Notebook Version
391
ret = '<html><!--notruncate-->\n'
392
ret += ' <table bgcolor=lightgrey cellpadding=0>\n'
393
ret += ' <tr>\n <td bgcolor=white align=center> '
394
ret += (' </td>\n <td bgcolor=white ' \
395
+ 'align=center> ').join(col_titles)
396
ret += ' </td>\n </tr>\n'
397
ret += '\n'.join([' <tr>\n ' + row_str(row, True) + ' </tr>' \
398
for row in cur])
399
ret += '\n </table>\n</html>'
400
else:
401
# Command Prompt Version
402
ret = ' '.join([col.ljust(max_field_size) for col in col_titles])
403
ret += '\n' + '-' * max_field_size * len(col_titles) + '\n'
404
ret += '\n'.join([row_str(row, False) for row in cur])
405
return ret
406
407
class SQLQuery(SageObject):
408
def __init__(self, database, *args, **kwds):
409
"""
410
A query for a SQLite database.
411
412
INPUT:
413
414
- ``database`` -- a SQLDatabase object
415
- ``query_dict`` -- a dictionary specifying the query itself. The
416
format is::
417
418
{'table_name':'tblname', 'display_cols':['col1', 'col2','col3'], 'expression': [col, operator, value]}
419
420
NOTE:
421
Every SQL type we are using is ultimately represented as a string,
422
so if you wish to save actual strings to a database, you actually
423
need to do something like: '"value"'.
424
425
See the documentation of ``SQLDatabase`` for an introduction to using
426
SQLite in Sage.
427
428
EXAMPLES::
429
430
sage: D = SQLDatabase()
431
sage: D.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
432
sage: D.add_data('simon',[(0,0),(1,2),(2,4)])
433
sage: r = SQLQuery(D, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 3]})
434
sage: r.show()
435
a1
436
--------------------
437
0
438
1
439
"""
440
if not isinstance(database, SQLDatabase):
441
raise TypeError('%s is not a valid SQLDatabase'%database)
442
self.__database__ = database
443
total_args = len(args) + len(kwds)
444
if total_args == 0:
445
self.__query_string__ = ''
446
self.__param_tuple__ = tuple()
447
self.__query_dict__ = {}
448
return
449
for x in args:
450
if isinstance(x,dict):
451
if 'query_dict' not in kwds:
452
kwds['query_dict'] = x
453
elif isinstance(x, str):
454
if 'query_string' not in kwds:
455
kwds['query_string'] = x
456
elif isinstance(x, tuple):
457
if 'param_tuple' not in kwds:
458
kwds['param_tuple'] = x
459
if total_args > 2 or not ('query_dict' in kwds or \
460
'query_string' in kwds) or ('query_dict' in kwds and\
461
('param_tuple' in kwds or 'query_string' in kwds)):
462
raise ValueError('Query must be constructed with either a ' \
463
+ 'dictionary or a string and tuple')
464
465
if 'query_dict' in kwds:
466
query_dict = kwds['query_dict']
467
else:
468
self.__query_string__ = kwds['query_string']
469
if 'param_tuple' in kwds:
470
self.__param_tuple__ = tuple((str(x) for x in kwds['param_tuple']))
471
else:
472
self.__param_tuple__ = tuple()
473
return
474
475
if query_dict:
476
skel = database.__skeleton__
477
if query_dict['table_name'] not in skel:
478
raise ValueError("Database has no table" \
479
+ str(query_dict['table_name']) + ".")
480
table_name = query_dict['table_name']
481
if query_dict['display_cols'] is not None:
482
for column in query_dict['display_cols']:
483
if column not in skel[table_name]:
484
raise ValueError("Table has no column %s."%column)
485
if query_dict['expression'][0] not in skel[table_name]:
486
raise ValueError("Table has no column " \
487
+ str(query_dict['expression'][0]) + ".")
488
489
self.__query_dict__ = query_dict
490
self.__param_tuple__ = (str(query_dict['expression'][2]),)
491
verify_operator(query_dict['expression'][1])
492
if query_dict['display_cols'] is None:
493
self.__query_string__ = 'SELECT , FROM %s WHERE '%table_name \
494
+ '%s.%s '%(table_name, query_dict['expression'][0]) \
495
+ '%s ?'%query_dict['expression'][1]
496
else:
497
query_dict['display_cols'] = ['%s.%s'%(table_name, x) \
498
for x in query_dict['display_cols']]
499
self.__query_string__ = 'SELECT ' \
500
+ ', '.join(query_dict['display_cols']) + ' FROM ' \
501
+ '%s WHERE %s.'%(table_name, table_name) \
502
+ '%s '%query_dict['expression'][0] \
503
+ '%s ?'%query_dict['expression'][1]
504
else:
505
self.__query_dict__ = {}
506
self.__param_tuple__ = tuple()
507
self.__query_string__ = ''
508
509
def __repr__(self):
510
"""
511
Overrides the print output to display useful info regarding the
512
query.
513
514
EXAMPLES::
515
516
sage: G = GraphDatabase()
517
sage: q = 'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data WHERE graph_id<=(?) AND num_vertices=(?)'
518
sage: param = (22,5)
519
sage: SQLQuery(G,q,param)
520
Query for sql database: ...graphs.db
521
Query string: SELECT graph_id,graph6,num_vertices,num_edges FROM
522
graph_data WHERE graph_id<=(?) AND num_vertices=(?)
523
Parameter tuple: ('22', '5')
524
sage: r = 'SELECT graph6 FROM graph_data WHERE num_vertices<=3'
525
sage: SQLQuery(G,r)
526
Query for sql database: ...graphs.db
527
Query string: SELECT graph6 FROM graph_data WHERE num_vertices<=3
528
"""
529
if not self.__query_string__:
530
return 'Empty query on %s.'%self.__database__.__dblocation__
531
return "Query for sql database: %s"%self.__database__.__dblocation__ \
532
+ "\nQuery string: %s"%self.__query_string__ \
533
+ ("\nParameter tuple: %s"%str(self.__param_tuple__) if \
534
self.__param_tuple__ else "")
535
536
def get_query_string(self):
537
"""
538
Returns a copy of the query string.
539
540
EXAMPLES::
541
542
sage: G = GraphDatabase()
543
sage: q = 'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data WHERE graph_id<=(?) AND num_vertices=(?)'
544
sage: param = (22,5)
545
sage: SQLQuery(G,q,param).get_query_string()
546
'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data
547
WHERE graph_id<=(?) AND num_vertices=(?)'
548
sage: r = 'SELECT graph6 FROM graph_data WHERE num_vertices<=3'
549
sage: SQLQuery(G,r).get_query_string()
550
'SELECT graph6 FROM graph_data WHERE num_vertices<=3'
551
"""
552
from copy import copy
553
return copy(self.__query_string__)
554
555
def __iter__(self):
556
"""
557
Returns an iterator over the results of the query.
558
559
EXAMPLES::
560
561
sage: G = GraphDatabase()
562
sage: q = 'SELECT graph_id,graph6 FROM graph_data WHERE num_vertices=(?)'
563
sage: param = (5,)
564
sage: Q = SQLQuery(G,q,param)
565
sage: it = Q.__iter__()
566
sage: it.next()
567
(18, u'D??')
568
sage: it.next()
569
(19, u'D?C')
570
sage: skip = [it.next() for _ in range(15)]
571
sage: it.next()
572
(35, u'DBk')
573
"""
574
try:
575
cur = self.__database__.__connection__.cursor()
576
return cur.execute(self.__query_string__, self.__param_tuple__)
577
except sqlite.OperationalError:
578
raise RuntimeError('Failure to fetch query.')
579
580
def query_results(self):
581
"""
582
Runs the query by executing the ``__query_string__``. Returns the
583
results of the query in a list.
584
585
EXAMPLES::
586
587
sage: G = GraphDatabase()
588
sage: q = 'SELECT graph_id,graph6,num_vertices,num_edges FROM graph_data WHERE graph_id<=(?) AND num_vertices=(?)'
589
sage: param = (22,5)
590
sage: Q = SQLQuery(G,q,param)
591
sage: Q.query_results()
592
[(18, u'D??', 5, 0), (19, u'D?C', 5, 1), (20, u'D?K', 5, 2),
593
(21, u'D@O', 5, 2), (22, u'D?[', 5, 3)]
594
sage: R = SQLQuery(G,{'table_name':'graph_data', 'display_cols':['graph6'], 'expression':['num_vertices','=',4]})
595
sage: R.query_results()
596
[(u'C?',), (u'C@',), (u'CB',), (u'CK',), (u'CF',), (u'CJ',),
597
(u'CL',), (u'CN',), (u'C]',), (u'C^',), (u'C~',)]
598
"""
599
return list(self)
600
601
def show(self, **kwds):
602
"""
603
Displays the result of the query in table format.
604
605
KEYWORDS:
606
607
- ``max_field_size`` -- how wide each field can be
608
- ``format_cols`` -- a dictionary that allows the user to specify the
609
format of a column's output by supplying a function. The format of
610
the dictionary is::
611
612
{'column_name':(lambda x: format_function(x))}
613
614
- ``plot_cols`` -- a dictionary that allows the user to specify that a
615
plot should be drawn by the object generated by a data slice. Note
616
that plot kwds are permitted. The dictionary format is::
617
618
{'column_name':((lambda x: plot_function(x)),**kwds)}
619
620
- ``relabel_cols`` -- a dictionary to specify a relabeling of column
621
headers. The dictionary format is::
622
623
{'table_name':{'old_col_name':'new_col_name'}}
624
625
- ``id_col`` -- reference to a column that can be used as an object
626
identifier for each row
627
628
EXAMPLES::
629
630
sage: DB = SQLDatabase()
631
sage: DB.create_table('simon',{'a1':{'sql':'bool', 'primary_key':False}, 'b2':{'sql':'int'}})
632
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
633
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
634
sage: r.show()
635
a1
636
--------------------
637
0
638
1
639
1
640
sage: D = GraphDatabase()
641
sage: from sage.graphs.graph_database import valid_kwds, data_to_degseq
642
sage: relabel = {}
643
sage: for col in valid_kwds:
644
... relabel[col] = ' '.join([word.capitalize() for word in col.split('_')])
645
sage: q = GraphQuery(display_cols=['graph6','degree_sequence'], num_vertices=4)
646
sage: SQLQuery.show(q, format_cols={'degree_sequence':(lambda x,y: data_to_degseq(x,y))}, relabel_cols=relabel, id_col='graph6')
647
Graph6 Degree Sequence
648
----------------------------------------
649
C? [0, 0, 0, 0]
650
C@ [0, 0, 1, 1]
651
CB [0, 1, 1, 2]
652
CF [1, 1, 1, 3]
653
CJ [0, 2, 2, 2]
654
CK [1, 1, 1, 1]
655
CL [1, 1, 2, 2]
656
CN [1, 2, 2, 3]
657
C] [2, 2, 2, 2]
658
C^ [2, 2, 3, 3]
659
C~ [3, 3, 3, 3]
660
"""
661
if not self.__query_string__: return self.__database__.show()
662
663
try:
664
cur = self.__database__.__connection__.cursor()
665
cur.execute(self.__query_string__, self.__param_tuple__)
666
except Exception:
667
raise RuntimeError('Failure to fetch query.')
668
669
print(_create_print_table(cur, [des[0] for des in cur.description], \
670
**kwds))
671
672
def __copy__(self):
673
"""
674
Returns a copy of itself.
675
676
EXAMPLES::
677
678
sage: G = GraphDatabase()
679
sage: Q = SQLQuery(G, {'table_name':'graph_data', 'display_cols':['graph_id','graph6','num_vertices'], 'expression':['num_edges','<',3]})
680
sage: R = copy(Q)
681
sage: R.__query_string__ = ''
682
sage: Q.__query_string__ == ''
683
False
684
"""
685
d = SQLQuery(self.__database__)
686
d.__query_dict__ = self.__query_dict__
687
d.__query_string__ = self.__query_string__
688
d.__param_tuple__ = self.__param_tuple__
689
return d
690
691
def intersect(self, other, join_table=None, join_dict=None, \
692
in_place=False):
693
"""
694
Returns a new ``SQLQuery`` that is the intersection of ``self`` and
695
``other``. ``join_table`` and ``join_dict`` can be ``None`` iff the
696
two queries only search one table in the database. All display columns
697
will be concatenated in order: self display cols + other display cols.
698
699
INPUT:
700
701
- ``other`` -- the ``SQLQuery`` to intersect with
702
- ``join_table`` -- base table to join on (This table should have at
703
least one column in each table to join on).
704
- ``join_dict`` -- a dictionary that represents the join structure for
705
the new query. (Must include a mapping for all tables, including
706
those previously joined in either query). Structure is given by::
707
708
{'join_table1':('corr_base_col1', 'col1'), 'join_table2':('corr_base_col2', 'col2')}
709
710
where ``join_table1`` is to be joined with ``join_table`` on
711
``join_table.corr_base_col1 = join_table1.col1``
712
713
EXAMPLES::
714
715
sage: DB = SQLDatabase()
716
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
717
sage: DB.create_table('lucy',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
718
sage: DB.add_data('simon', [(0,5),(1,4)])
719
sage: DB.add_data('lucy', [(1,1),(1,4)])
720
sage: q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['b2'], 'expression':['a1','=',1]})
721
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
722
sage: s = q.intersect(r, 'simon', {'lucy':('a1','a1')})
723
sage: s.get_query_string()
724
'SELECT lucy.b2,simon.a1 FROM simon INNER JOIN lucy ON
725
simon.a1=lucy.a1 WHERE ( lucy.a1 = ? ) AND ( simon.b2 <= ? )'
726
sage: s.query_results()
727
[(1, 1), (4, 1)]
728
sage: s = q.intersect(r)
729
Traceback (most recent call last):
730
...
731
ValueError: Input queries query different tables but join
732
parameters are NoneType
733
sage: s.__query_string__ == q.__query_string__
734
False
735
sage: q.intersect(r, 'simon', {'lucy':('a1','a1')}, True)
736
sage: q.__query_string__ == s.__query_string__
737
True
738
"""
739
if self.__query_dict__ is None or other.__query_dict__ is None:
740
raise RuntimeError('Queries must be constructed using a ' \
741
+ 'dictionary in order to be intersected.')
742
if self.__database__ != other.__database__:
743
raise TypeError('Queries %s and %s must be '%(self, other) \
744
+ 'attached to the same database.')
745
746
if in_place:
747
if not self.__query_string__:
748
self.__query_string__ = other.__query_string__
749
self.__param_tuple__ = other.__param_tuple__
750
elif not other.__query_string__: return
751
else:
752
self._merge_queries(other, self, join_table, join_dict, 'AND')
753
else:
754
from copy import copy
755
if not self.__query_string__: return copy(other)
756
if not other.__query_string__: return copy(self)
757
return self._merge_queries(other, copy(self), join_table, \
758
join_dict, 'AND')
759
760
def _merge_queries(self, other, ret, join_table, join_dict, operator):
761
"""
762
The query ``ret`` is set to a new ``SQLQuery`` that is combines self
763
and other through ``operator``. The other arguments are the same as
764
``intersect`` and ``union``.
765
766
EXAMPLES::
767
768
sage: DB = SQLDatabase()
769
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
770
sage: DB.create_table('lucy',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
771
sage: DB.add_data('simon', [(0,5),(1,4)])
772
sage: DB.add_data('lucy', [(1,1),(1,4)])
773
sage: q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['b2'], 'expression':['a1','=',1]})
774
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
775
sage: s = q._merge_queries(r, copy(q), 'simon', {'lucy':('a1','a1')}, 'OR')
776
sage: s.get_query_string()
777
'SELECT lucy.b2,simon.a1 FROM simon INNER JOIN lucy ON
778
simon.a1=lucy.a1 WHERE ( lucy.a1 = ? ) OR ( simon.b2 <= ? )'
779
sage: s.query_results()
780
[(1, 1), (4, 1)]
781
"""
782
if join_table is None or join_dict is None:
783
pattern = ' JOIN '
784
if re.search(pattern, self.__query_string__) \
785
or re.search(pattern, other.__query_string__):
786
raise TypeError('Input queries have joins but join ' \
787
+ 'paramaters are NoneType')
788
s = ((self.__query_string__).upper()).split('FROM ')
789
o = ((other.__query_string__).upper()).split('FROM ')
790
s = s[1].split(' WHERE ')
791
o = o[1].split(' WHERE ')
792
if s[0] != o[0]:
793
raise ValueError('Input queries query different tables but ' \
794
+ 'join parameters are NoneType')
795
796
# inner join clause
797
if join_dict is not None:
798
joins = join_table
799
for table in join_dict:
800
joins += ' INNER JOIN %s ON %s.'%(table, join_table) \
801
+ '%s=%s.'%(join_dict[table][0], table) \
802
+ '%s '%join_dict[table][1]
803
ret.__query_string__ = re.sub(' FROM .* WHERE ', ' FROM ' + joins \
804
+ 'WHERE ', self.__query_string__)
805
806
# concatenate display cols
807
disp1 = ret.__query_string__.split(' FROM')
808
disp2 = other.__query_string__.split(' FROM')
809
disp1.insert(1, ',%s FROM'%disp2[0].split('SELECT ')[1])
810
new_query = ''.join(disp1)
811
812
# concatenate where clause
813
new_query = re.sub(' WHERE ',' WHERE ( ', new_query)
814
new_query += re.sub('^.* WHERE ',' ) %s ( '%operator, \
815
other.__query_string__)
816
ret.__query_string__ = new_query + ' )'
817
818
ret.__param_tuple__ = self.__param_tuple__ + other.__param_tuple__
819
820
return ret
821
822
def union(self, other, join_table=None, join_dict=None, in_place=False):
823
"""
824
Returns a new ``SQLQuery`` that is the union of self and other.
825
``join_table`` and ``join_dict`` can be ``None`` iff the two queries
826
only search one table in the database. All display columns will be
827
concatenated in order: self display cols + other display cols.
828
829
INPUT:
830
831
- ``other`` -- the ``SQLQuery`` to union with
832
- ``join_table`` -- base table to join on (This table should have at
833
least one column in each table to join on).
834
- ``join_dict`` -- a dictionary that represents the join structure for
835
the new query. (Must include a mapping for all tables, including
836
those previously joined in either query). Structure is given by::
837
838
{'join_table1':('corr_base_col1', 'col1'), 'join_table2':('corr_base_col2', 'col2')}
839
840
where ``join_table1` is to be joined with ``join_table`` on
841
``join_table.corr_base_col1=join_table1.col1``
842
843
EXAMPLES::
844
845
sage: DB = SQLDatabase()
846
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
847
sage: DB.create_table('lucy',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
848
sage: DB.add_data('simon', [(0,5),(1,4)])
849
sage: DB.add_data('lucy', [(1,1),(1,4)])
850
sage: q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['b2'], 'expression':['a1','=',1]})
851
sage: r = SQLQuery(DB, {'table_name':'simon', 'display_cols':['a1'], 'expression':['b2','<=', 6]})
852
sage: s = q.union(r, 'simon', {'lucy':('a1','a1')})
853
sage: s.get_query_string()
854
'SELECT lucy.b2,simon.a1 FROM simon INNER JOIN lucy ON
855
simon.a1=lucy.a1 WHERE ( lucy.a1 = ? ) OR ( simon.b2 <= ? )'
856
sage: s.query_results()
857
[(1, 1), (4, 1)]
858
"""
859
if self.__query_dict__ is None or other.__query_dict__ is None:
860
raise RuntimeError('Queries must be constructed using a ' \
861
+ 'dictionary in order to be unioned.')
862
if self.__database__ != other.__database__:
863
raise TypeError('Queries %s and %s must be '%(self, other) \
864
+ 'attached to the same database.')
865
866
if in_place:
867
if self.__query_string__ and other.__query_string__:
868
self._merge_queries(other, self, join_table, join_dict, 'OR')
869
else:
870
from copy import copy
871
if not self.__query_string__: return copy(self)
872
if not other.__query_string__: return copy(other)
873
return self._merge_queries(other, copy(self), join_table, \
874
join_dict, 'OR')
875
876
class SQLDatabase(SageObject):
877
def __init__(self, filename=None, read_only=None, skeleton=None):
878
r"""
879
A SQL Database object corresponding to a database file.
880
881
INPUT:
882
883
- ``filename`` -- a string
884
- ``skeleton`` -- a triple-indexed dictionary
885
886
- outer key -- table name
887
888
- inner key -- column name
889
890
- inner inner key -- one of the following:
891
892
- ``primary_key`` -- boolean, whether column has been
893
set as primary key
894
- ``index`` -- boolean, whether column has been set as
895
index
896
- ``unique`` -- boolean, whether column has been set as
897
unique
898
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
899
``'INTEGER'``, ``'REAL'``, or other user defined type
900
901
TUTORIAL:
902
903
The ``SQLDatabase`` class is for interactively building databases
904
intended for queries. This may sound redundant, but it is important. If
905
you want a database intended for quick lookup of entries in very large
906
tables, much like a hash table (such as a Python dictionary), a
907
``SQLDatabase`` may not be what you are looking for. The strength of
908
``SQLDatabases`` is in queries, searches through the database with
909
complicated criteria.
910
911
For example, we create a new database for storing isomorphism classes
912
of simple graphs::
913
914
sage: D = SQLDatabase()
915
916
In order to generate representatives for the classes, we will import a
917
function which generates all labeled graphs (noting that this is not
918
the optimal way)::
919
920
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import all_labeled_graphs
921
922
We will need a table in the database in which to store the graphs, and
923
we specify its structure with a Python dictionary, each of whose keys
924
is the name of a column::
925
926
sage: table_skeleton = {
927
... 'graph6':{'sql':'TEXT', 'index':True, 'primary_key':True},
928
... 'vertices':{'sql':'INTEGER'},
929
... 'edges':{'sql':'INTEGER'}
930
... }
931
932
Then we create the table::
933
934
sage: D.create_table('simon', table_skeleton)
935
sage: D.show('simon')
936
edges graph6 vertices
937
------------------------------------------------------------
938
939
Now that we have the table, we will begin to populate the table with
940
rows. First, add the graph on zero vertices.::
941
942
sage: G = Graph()
943
sage: D.add_row('simon',(0, G.graph6_string(), 0))
944
sage: D.show('simon')
945
edges graph6 vertices
946
------------------------------------------------------------
947
0 ? 0
948
949
Next, add the graph on one vertex.::
950
951
sage: G.add_vertex()
952
0
953
sage: D.add_row('simon',(0, G.graph6_string(), 1))
954
sage: D.show('simon')
955
edges graph6 vertices
956
------------------------------------------------------------
957
0 ? 0
958
0 @ 1
959
960
Say we want a database of graphs on four or less vertices::
961
962
sage: labels = {}
963
sage: for i in range(2, 5):
964
... labels[i] = []
965
... for g in all_labeled_graphs(i):
966
... g = g.canonical_label()
967
... if g not in labels[i]:
968
... labels[i].append(g)
969
... D.add_row('simon', (g.size(), g.graph6_string(), g.order()))
970
sage: D.show('simon')
971
edges graph6 vertices
972
------------------------------------------------------------
973
0 ? 0
974
0 @ 1
975
0 A? 2
976
1 A_ 2
977
0 B? 3
978
1 BG 3
979
2 BW 3
980
3 Bw 3
981
0 C? 4
982
1 C@ 4
983
2 CB 4
984
3 CF 4
985
3 CJ 4
986
2 CK 4
987
3 CL 4
988
4 CN 4
989
4 C] 4
990
5 C^ 4
991
6 C~ 4
992
993
We can then query the database -- let's ask for all the graphs on four
994
vertices with three edges. We do so by creating two queries and asking
995
for rows that satisfy them both::
996
997
sage: Q = SQLQuery(D, {'table_name':'simon', 'display_cols':['graph6'], 'expression':['vertices','=',4]})
998
sage: Q2 = SQLQuery(D, {'table_name':'simon', 'display_cols':['graph6'], 'expression':['edges','=',3]})
999
sage: Q = Q.intersect(Q2)
1000
sage: Q.query_results()
1001
[(u'CF', u'CF'), (u'CJ', u'CJ'), (u'CL', u'CL')]
1002
1003
NOTE: The values of ``display_cols`` are always concatenated in
1004
intersections and unions.
1005
1006
Of course, we can save the database to file::
1007
1008
sage: replace_with_your_own_filepath = tmp_dir()
1009
sage: D.save(replace_with_your_own_filepath + 'simon.db')
1010
1011
Now the database's hard link is to this file, and not the temporary db
1012
file. For example, let's say we open the same file with another class
1013
instance. We can load the file as an immutable database::
1014
1015
sage: E = SQLDatabase(replace_with_your_own_filepath + 'simon.db')
1016
sage: E.show('simon')
1017
edges graph6 vertices
1018
------------------------------------------------------------
1019
0 ? 0
1020
0 @ 1
1021
0 A? 2
1022
1 A_ 2
1023
0 B? 3
1024
1 BG 3
1025
2 BW 3
1026
3 Bw 3
1027
0 C? 4
1028
1 C@ 4
1029
2 CB 4
1030
3 CF 4
1031
3 CJ 4
1032
2 CK 4
1033
3 CL 4
1034
4 CN 4
1035
4 C] 4
1036
5 C^ 4
1037
6 C~ 4
1038
sage: E.drop_table('simon')
1039
Traceback (most recent call last):
1040
...
1041
RuntimeError: Cannot drop tables from a read only database.
1042
"""
1043
if filename is None:
1044
if read_only is None:
1045
read_only = False
1046
filename = tmp_filename() + '.db'
1047
elif (filename[-3:] != '.db'):
1048
raise ValueError('Please enter a valid database path (file name ' \
1049
+ '%s does not end in .db).'%filename)
1050
if read_only is None:
1051
read_only = True
1052
1053
self.__read_only__ = read_only
1054
self.ignore_warnings = False
1055
self.__dblocation__ = filename
1056
self.__connection__ = sqlite.connect(self.__dblocation__, \
1057
check_same_thread=False)
1058
# this is to avoid the multiple thread problem with dsage:
1059
# pysqlite does not trust multiple threads for the same connection
1060
self.__connection__.create_function("regexp", 2, regexp)
1061
1062
# construct skeleton (from provided database)
1063
self.__skeleton__ = construct_skeleton(self)
1064
1065
# add bones from new skeleton to database,
1066
# without changing existing structure
1067
if skeleton is not None and not read_only:
1068
for table in skeleton:
1069
if table not in self.__skeleton__:
1070
self.create_table(table, skeleton[table])
1071
else:
1072
for column in skeleton[table]:
1073
if column not in self.__skeleton__[table]:
1074
self.add_column(table, column, \
1075
skeleton[table][column])
1076
else:
1077
print('Column attributes were ignored for ' \
1078
'table {}, column {} -- column is ' \
1079
'already in table.'.format(table, column))
1080
elif skeleton is not None:
1081
raise RuntimeError('Cannot update skeleton of a read only ' \
1082
+ 'database.')
1083
1084
def __repr__(self):
1085
"""
1086
Overrides the print output to display useful info regarding the
1087
database.
1088
1089
EXAMPLES::
1090
1091
sage: replace_with_filepath = tmp_dir() + 'test.db'
1092
sage: SD = SQLDatabase(replace_with_filepath, False)
1093
sage: SD.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
1094
sage: print SD
1095
table simon:
1096
column n: index: True; unique: False; primary_key: False;
1097
sql: INTEGER;
1098
"""
1099
s = ''
1100
for table in self.__skeleton__:
1101
s += 'table ' + table + ':\n'
1102
for column in self.__skeleton__[table]:
1103
s += ' column ' + column + ': '
1104
for data in self.__skeleton__[table][column]:
1105
s += data + ': ' \
1106
+ str(self.__skeleton__[table][column][data]) + '; '
1107
s += '\n'
1108
return s
1109
1110
def __copy__(self):
1111
"""
1112
Returns an instance of ``SQLDatabase`` that points to a copy database,
1113
and allows modification.
1114
1115
EXAMPLES::
1116
1117
sage: DB = SQLDatabase()
1118
sage: DB.create_table('lucy',{'id':{'sql':'INTEGER', 'primary_key':True, 'index':True}, 'a1':{'sql':'bool'}, 'b2':{'sql':'int', 'primary_key':False}})
1119
sage: DB.add_rows('lucy', [(0,1,1),(1,1,4),(2,0,7),(3,1,384), (4,1,978932)],['id','a1','b2'])
1120
sage: d = copy(DB)
1121
sage: d == DB
1122
False
1123
sage: d.show('lucy')
1124
a1 id b2
1125
------------------------------------------------------------
1126
1 0 1
1127
1 1 4
1128
0 2 7
1129
1 3 384
1130
1 4 978932
1131
sage: DB.show('lucy')
1132
a1 id b2
1133
------------------------------------------------------------
1134
1 0 1
1135
1 1 4
1136
0 2 7
1137
1 3 384
1138
1 4 978932
1139
"""
1140
# copy .db file
1141
new_loc = tmp_filename() + '.db'
1142
if not self.__read_only__:
1143
self.commit()
1144
os.system('cp '+ self.__dblocation__ + ' ' + new_loc)
1145
D = SQLDatabase(filename=new_loc, read_only=False)
1146
return D
1147
1148
def save(self, filename):
1149
"""
1150
Save the database to the specified location.
1151
1152
EXAMPLES::
1153
1154
sage: MonicPolys = SQLDatabase()
1155
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
1156
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
1157
sage: tmp = tmp_dir() # replace with your own file path
1158
sage: MonicPolys.save(tmp+'sage.db')
1159
sage: N = SQLDatabase(tmp+'sage.db')
1160
sage: N.show('simon')
1161
n
1162
--------------------
1163
0
1164
1
1165
2
1166
3
1167
4
1168
5
1169
6
1170
7
1171
8
1172
9
1173
10
1174
11
1175
12
1176
13
1177
14
1178
15
1179
16
1180
17
1181
18
1182
19
1183
"""
1184
if not self.__read_only__:
1185
self.commit()
1186
os.system('cp ' + self.__dblocation__ + ' ' + filename)
1187
1188
def get_skeleton(self, check=False):
1189
"""
1190
Returns a dictionary representing the hierarchical structure of the
1191
database, in the following format.
1192
1193
- skeleton -- a triple-indexed dictionary
1194
1195
- outer key -- table name
1196
1197
- inner key -- column name
1198
1199
- inner inner key -- one of the following:
1200
1201
- ``primary_key`` -- boolean, whether column has been
1202
set as primary key
1203
- ``index`` -- boolean, whether column has been set as
1204
index
1205
- ``unique`` -- boolean, whether column has been set as
1206
unique
1207
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
1208
``'INTEGER'``, ``'REAL'``, or other user defined type
1209
1210
For example::
1211
1212
{'table1':{'col1':{'primary_key':False, 'index':True, 'unique': False,'sql':'REAL'}}}
1213
1214
INPUT:
1215
1216
- ``check`` -- if True, checks to make sure the database's actual
1217
structure matches the skeleton on record.
1218
1219
EXAMPLES::
1220
1221
sage: GDB = GraphDatabase()
1222
sage: GDB.get_skeleton() # slightly random output
1223
{u'aut_grp': {u'aut_grp_size': {'index': True,
1224
'unique': False,
1225
'primary_key': False,
1226
'sql': u'INTEGER'},
1227
...
1228
u'num_vertices': {'index': True,
1229
'unique': False,
1230
'primary_key': False,
1231
'sql': u'INTEGER'}}}
1232
"""
1233
if check:
1234
d = construct_skeleton(self)
1235
if d == self.__skeleton__:
1236
return d
1237
else:
1238
raise RuntimeError("Skeleton structure is out of whack!")
1239
return self.__skeleton__
1240
1241
def query(self, *args, **kwds):
1242
"""
1243
Creates a ``SQLQuery`` on this database. For full class details,
1244
type ``SQLQuery?`` and press shift+enter.
1245
1246
EXAMPLES::
1247
1248
sage: D = SQLDatabase()
1249
sage: D.create_table('simon', {'wolf':{'sql':'BOOLEAN'}, 'tag':{'sql':'INTEGER'}})
1250
sage: q = D.query({'table_name':'simon', 'display_cols':['tag'], 'expression':['wolf','=',1]})
1251
sage: q.get_query_string()
1252
'SELECT simon.tag FROM simon WHERE simon.wolf = ?'
1253
sage: q.__param_tuple__
1254
('1',)
1255
sage: q = D.query(query_string='SELECT tag FROM simon WHERE wolf=?',param_tuple=(1,))
1256
sage: q.get_query_string()
1257
'SELECT tag FROM simon WHERE wolf=?'
1258
sage: q.__param_tuple__
1259
('1',)
1260
"""
1261
return SQLQuery(self, *args, **kwds)
1262
1263
__call__ = query
1264
1265
def show(self, table_name, **kwds):
1266
"""
1267
Show an entire table from the database.
1268
1269
EXAMPLES::
1270
1271
sage: DB = SQLDatabase()
1272
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
1273
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
1274
sage: DB.show('simon')
1275
a1 b2
1276
----------------------------------------
1277
0 0
1278
1 1
1279
1 2
1280
"""
1281
try:
1282
cur = self.__connection__.cursor()
1283
cur.execute('SELECT * FROM ' + table_name)
1284
except Exception:
1285
raise RuntimeError('Failure to fetch data.')
1286
print(_create_print_table(cur, [des[0] for des in cur.description], \
1287
**kwds))
1288
1289
def get_cursor(self, ignore_warning=None):
1290
"""
1291
Returns a pysqlite cursor for the database connection.
1292
1293
A cursor is an input from which you can execute sqlite commands on the
1294
database.
1295
1296
Recommended for more advanced users only.
1297
1298
EXAMPLES::
1299
1300
sage: DB = SQLDatabase()
1301
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
1302
sage: DB.add_row('simon',(0,1))
1303
sage: DB.add_rows('simon',[(0,0),(1,1),(1,2)])
1304
sage: DB.add_rows('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
1305
sage: cur = DB.get_cursor()
1306
sage: (cur.execute('select * from simon')).fetchall()
1307
[(0, 1), (0, 0), (1, 1), (1, 2), (0, 0), (0, 4), (1, 5)]
1308
"""
1309
if self.__read_only__:
1310
if ignore_warning is None:
1311
ignore_warning = self.ignore_warnings
1312
if not ignore_warning:
1313
import warnings
1314
warnings.warn('Database is read only, using the cursor can ' \
1315
+ 'alter the stored data. Set self.ignore_warnings to ' \
1316
+ 'True in order to mute future warnings.', RuntimeWarning)
1317
return self.__connection__.cursor()
1318
1319
def get_connection(self, ignore_warning=None):
1320
"""
1321
Returns a pysqlite connection to the database.
1322
1323
You most likely want ``get_cursor()`` instead, which is used for
1324
executing sqlite commands on the database.
1325
1326
Recommended for more advanced users only.
1327
1328
EXAMPLES::
1329
1330
sage: D = SQLDatabase(read_only=True)
1331
sage: con = D.get_connection()
1332
doctest:...: RuntimeWarning: Database is read only, using the connection can alter the stored data. Set self.ignore_warnings to True in order to mute future warnings.
1333
sage: con = D.get_connection(True)
1334
sage: D.ignore_warnings = True
1335
sage: con = D.get_connection()
1336
sage: t = con.execute('CREATE TABLE simon(n INTEGER, n2 INTEGER)')
1337
sage: for n in range(10):
1338
... t = con.execute('INSERT INTO simon VALUES(%d,%d)'%(n,n^2))
1339
sage: D.show('simon')
1340
n n2
1341
----------------------------------------
1342
0 0
1343
1 1
1344
2 4
1345
3 9
1346
4 16
1347
5 25
1348
6 36
1349
7 49
1350
8 64
1351
9 81
1352
"""
1353
if self.__read_only__:
1354
if ignore_warning is None:
1355
ignore_warning = self.ignore_warnings
1356
if not ignore_warning:
1357
import warnings
1358
warnings.warn('Database is read only, using the connection ' \
1359
+ 'can alter the stored data. Set self.ignore_warnings ' \
1360
+ 'to True in order to mute future warnings.', \
1361
RuntimeWarning)
1362
return self.__connection__
1363
1364
def create_table(self, table_name, table_skeleton):
1365
"""
1366
Creates a new table in the database.
1367
1368
To create a table, a column structure must be specified. The form for
1369
this is a Python dict, for example::
1370
1371
{'col1': {'sql':'INTEGER', 'index':False, 'unique':True, 'primary_key':False}, ...}
1372
1373
INPUT:
1374
1375
- ``table_name`` -- a string
1376
- ``table_skeleton`` -- a double-indexed dictionary
1377
1378
- outer key -- column name
1379
1380
- inner key -- one of the following:
1381
1382
- ``primary_key`` -- boolean, whether column has been set
1383
asprimary key
1384
- ``index`` -- boolean, whether column has been set as
1385
index
1386
- ``unique`` -- boolean, whether column has been set as
1387
unique
1388
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
1389
``'INTEGER'``, ``'REAL'``, or other user defined type
1390
1391
NOTE:
1392
1393
Some SQL features, such as automatically incrementing primary key,
1394
require the full word ``'INTEGER'``, not just ``'INT'``.
1395
1396
EXAMPLES::
1397
1398
sage: D = SQLDatabase()
1399
sage: table_skeleton = {
1400
... 'graph6':{'sql':'TEXT', 'index':True, 'primary_key':True},
1401
... 'vertices':{'sql':'INTEGER'},
1402
... 'edges':{'sql':'INTEGER'}
1403
... }
1404
sage: D.create_table('simon', table_skeleton)
1405
sage: D.show('simon')
1406
edges graph6 vertices
1407
------------------------------------------------------------
1408
"""
1409
if self.__read_only__:
1410
raise RuntimeError('Cannot add table to a read only database.')
1411
if table_name in self.__skeleton__:
1412
raise ValueError('Database already has a table named' \
1413
+ '%s.'%table_name)
1414
if table_name.find(' ') != -1:
1415
raise ValueError('Table names cannot contain spaces.')
1416
if table_name.upper() in sqlite_keywords:
1417
raise ValueError('Table names cannot be a SQLite keyword.')
1418
create_statement = 'CREATE TABLE ' + table_name + '('
1419
statement = []
1420
index_statement = ''
1421
for col in table_skeleton:
1422
if col.find('sqlite') != -1:
1423
raise ValueError("Column names cannot contain 'sqlite'.")
1424
if col.upper() in sqlite_keywords:
1425
raise ValueError('Column names cannot be a SQLite keyword.')
1426
table_skeleton[col] = verify_column(table_skeleton[col])
1427
typ = table_skeleton[col]['sql']
1428
if verify_type(typ):
1429
if typ.upper() == 'NOTYPE':
1430
typ = ''
1431
if table_skeleton[col]['primary_key']:
1432
statement.append(col + ' ' + typ + ' PRIMARY KEY')
1433
elif table_skeleton[col]['unique']:
1434
statement.append(col + ' ' + typ + ' UNIQUE')
1435
else:
1436
statement.append(col + ' ' + typ)
1437
if table_skeleton[col]['index']:
1438
index_statement += 'CREATE INDEX i_%s_%s'%(table_name,\
1439
col) + ' ON %s(%s);\n'%(table_name, col)
1440
create_statement += ', '.join(statement) + ') '
1441
1442
self.__connection__.execute(create_statement)
1443
if index_statement:
1444
self.__connection__.executescript(index_statement)
1445
1446
self.__skeleton__[table_name] = table_skeleton
1447
1448
def add_column(self, table_name, col_name, col_dict, default='NULL'):
1449
"""
1450
Add a column named ``col_name`` to table ``table_name``, whose data
1451
types are described by ``col_dict``. The format for this is::
1452
1453
{'col1':{'primary_key':False, 'unique': True, 'index':True, 'sql':'REAL'}}
1454
1455
INPUT:
1456
1457
- ``col_dict`` -- a dictionary:
1458
1459
- key -- column name
1460
1461
- inner key -- one of the following:
1462
1463
- ``primary_key`` -- boolean, whether column has been set
1464
as primary key
1465
- ``index`` -- boolean, whether column has been set as
1466
index
1467
- ``unique`` -- boolean, weather column has been set as
1468
unique
1469
- ``sql`` -- one of ``'TEXT'``, ``'BOOLEAN'``,
1470
``'INTEGER'``, ``'REAL'``, or other user defined type
1471
1472
EXAMPLES::
1473
1474
sage: MonicPolys = SQLDatabase()
1475
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
1476
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
1477
sage: MonicPolys.add_column('simon', 'n_squared', {'sql':'INTEGER', 'index':False}, 0)
1478
sage: MonicPolys.show('simon')
1479
n_squared n
1480
----------------------------------------
1481
0 0
1482
0 1
1483
0 2
1484
0 3
1485
0 4
1486
0 5
1487
0 6
1488
0 7
1489
0 8
1490
0 9
1491
0 10
1492
0 11
1493
0 12
1494
0 13
1495
0 14
1496
0 15
1497
0 16
1498
0 17
1499
0 18
1500
0 19
1501
"""
1502
if self.__read_only__:
1503
raise RuntimeError('Cannot add columns to a read only database.')
1504
# Check input:
1505
if col_name.find('sqlite') != -1:
1506
raise ValueError("Column names cannot contain 'sqlite'.")
1507
if col_name.upper() in sqlite_keywords:
1508
raise ValueError("Column names cannot be SQLite keywords.")
1509
if table_name not in self.__skeleton__:
1510
raise ValueError("Database has no table %s."%table_name)
1511
if col_name in self.__skeleton__[table_name]:
1512
raise ValueError("Table %s already has column %s."%(table_name,col_name))
1513
1514
# Update the skeleton:
1515
self.__skeleton__[table_name][col_name] = verify_column(col_dict)
1516
1517
try:
1518
self._rebuild_table(table_name, col_name, default)
1519
except sqlite.Error as e:
1520
# delete added column from skeleton
1521
self.__skeleton__[table_name].pop(col_name)
1522
1523
print('A sqlite error occurred: ', e.args[0])
1524
1525
def _rebuild_table(self, table_name, col_name=None, default=''):
1526
"""
1527
Rebuilds the table ``table_name`` adding column ``col_name`` if not
1528
``None``. If a new column is added, each rows' value is set to
1529
``default``.
1530
1531
Used in the methods ``add_column``, ``drop_column``, ``make_unique``,
1532
``drop_unique``, ``make_primary_key``, and ``drop_primary_key`` to get
1533
around the limitations of SQLite's ``ALTER TABLE``. We have set up the
1534
creation of a temporary database in this method. Please feel free to
1535
improve on this by sending a patch or suggestion.
1536
1537
EXAMPLES::
1538
1539
sage: MonicPolys = SQLDatabase()
1540
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
1541
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
1542
sage: MonicPolys.show('simon')
1543
n
1544
--------------------
1545
0
1546
1
1547
2
1548
3
1549
4
1550
5
1551
6
1552
7
1553
8
1554
9
1555
10
1556
11
1557
12
1558
13
1559
14
1560
15
1561
16
1562
17
1563
18
1564
19
1565
sage: MonicPolys._rebuild_table('simon')
1566
sage: MonicPolys.show('simon')
1567
n
1568
--------------------
1569
0
1570
1
1571
2
1572
3
1573
4
1574
5
1575
6
1576
7
1577
8
1578
9
1579
10
1580
11
1581
12
1582
13
1583
14
1584
15
1585
16
1586
17
1587
18
1588
19
1589
"""
1590
cols = []
1591
cols_attr = []
1592
table_skeleton = self.__skeleton__[table_name]
1593
# gather column names and attributes for new table
1594
for col in table_skeleton:
1595
cols.append(col)
1596
attr_str = col + ' ' + table_skeleton[col]['sql']
1597
if table_skeleton[col]['primary_key']:
1598
attr_str += ' PRIMARY KEY'
1599
elif table_skeleton[col]['unique']:
1600
attr_str += ' UNIQUE'
1601
cols_attr.append(attr_str)
1602
1603
original = list(cols)
1604
1605
if col_name is not None:
1606
original[original.index(col_name)] = str(default)
1607
1608
original = ', '.join(original)
1609
cols = ', '.join(cols)
1610
cols_attr = ', '.join(cols_attr)
1611
1612
# Silly SQLite -- we have to make a temp table to hold info...
1613
self.__connection__.executescript("""
1614
CREATE TEMPORARY TABLE spam(%s);
1615
INSERT INTO spam SELECT %s FROM %s;
1616
DROP TABLE %s;
1617
CREATE TABLE %s (%s);
1618
"""%(cols_attr, original, table_name, table_name, table_name, cols_attr))
1619
# Update indices in new table
1620
index_statement = ''.join(['CREATE INDEX i_%s_%s ON '%(table_name, \
1621
col) + '%s(%s);\n'%(table_name, col) for col in \
1622
self.__skeleton__[table_name] if \
1623
self.__skeleton__[table_name][col]['index'] and not \
1624
self.__skeleton__[table_name][col]['primary_key']])
1625
if index_statement: self.__connection__.executescript(index_statement)
1626
1627
# Now we can plop our data into the *new* table:
1628
self.__connection__.executescript("""
1629
INSERT INTO %s SELECT %s FROM spam;
1630
DROP TABLE spam;
1631
"""%(table_name, cols))
1632
1633
self.vacuum()
1634
1635
def drop_column(self, table_name, col_name):
1636
"""
1637
Drop the column ``col_name`` from table ``table_name``.
1638
1639
EXAMPLES::
1640
1641
sage: MonicPolys = SQLDatabase()
1642
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}})
1643
sage: for n in range(20): MonicPolys.add_row('simon', (n,))
1644
sage: MonicPolys.add_column('simon', 'n_squared', {'sql':'INTEGER'}, 0)
1645
sage: MonicPolys.drop_column('simon', 'n_squared')
1646
sage: MonicPolys.show('simon')
1647
n
1648
--------------------
1649
0
1650
1
1651
2
1652
3
1653
4
1654
5
1655
6
1656
7
1657
8
1658
9
1659
10
1660
11
1661
12
1662
13
1663
14
1664
15
1665
16
1666
17
1667
18
1668
19
1669
"""
1670
if self.__read_only__:
1671
raise RuntimeError('Cannot drop columns in a read only database.')
1672
# Check input:
1673
if table_name not in self.__skeleton__:
1674
raise ValueError("Database has no table %s."%table_name)
1675
if col_name not in self.__skeleton__[table_name]:
1676
raise ValueError("Table %s has no column %s."%(table_name,col_name))
1677
1678
# Update the skeleton:
1679
self.__skeleton__[table_name].pop(col_name)
1680
1681
self._rebuild_table(table_name)
1682
1683
def rename_table(self, table_name, new_name):
1684
"""
1685
Renames the table ``table_name`` to ``new_name``.
1686
1687
EXAMPLES::
1688
1689
sage: D = SQLDatabase()
1690
sage: D.create_table('simon',{'col1':{'sql':'INTEGER'}})
1691
sage: D.show('simon')
1692
col1
1693
--------------------
1694
sage: D.rename_table('simon', 'lucy')
1695
sage: D.show('simon')
1696
Traceback (most recent call last):
1697
...
1698
RuntimeError: Failure to fetch data.
1699
sage: D.show('lucy')
1700
col1
1701
--------------------
1702
1703
"""
1704
if self.__read_only__:
1705
raise RuntimeError('Cannot rename tables in a read only database.')
1706
# Check input:
1707
if table_name not in self.__skeleton__:
1708
raise ValueError('Database has no table %s.'%table_name)
1709
if new_name in self.__skeleton__:
1710
raise ValueError('Database already has table %s.'%new_name)
1711
1712
self.__connection__.execute('ALTER TABLE %s RENAME TO '%table_name \
1713
+ new_name)
1714
1715
# Update skeleton:
1716
self.__skeleton__[new_name] = self.__skeleton__.pop(table_name)
1717
1718
def drop_table(self, table_name):
1719
"""
1720
Delete table ``table_name`` from database.
1721
1722
INPUT:
1723
1724
- ``table_name`` -- a string
1725
1726
EXAMPLES::
1727
1728
sage: D = SQLDatabase()
1729
sage: D.create_table('simon',{'col1':{'sql':'INTEGER'}})
1730
sage: D.show('simon')
1731
col1
1732
--------------------
1733
sage: D.drop_table('simon')
1734
sage: D.get_skeleton()
1735
{}
1736
"""
1737
if self.__read_only__:
1738
raise RuntimeError('Cannot drop tables from a read only ' \
1739
+ 'database.')
1740
if table_name not in self.__skeleton__:
1741
raise ValueError("Database has no table %s."%table_name)
1742
1743
self.__connection__.execute('DROP TABLE ' + table_name)
1744
1745
# Update Skeleton
1746
self.__skeleton__.pop(table_name)
1747
1748
def drop_data_from_table(self, table_name):
1749
"""
1750
Removes all rows from ``table_name``.
1751
1752
EXAMPLES::
1753
1754
sage: D = SQLDatabase()
1755
sage: D.create_table('simon',{'col1':{'sql':'INTEGER'}})
1756
sage: D.add_row('simon',(9,))
1757
sage: D.show('simon')
1758
col1
1759
--------------------
1760
9
1761
sage: D.drop_data_from_table('simon')
1762
sage: D.show('simon')
1763
col1
1764
--------------------
1765
"""
1766
if self.__read_only__:
1767
raise RuntimeError('Cannot remove data from a read only database.')
1768
if table_name not in self.__skeleton__:
1769
raise ValueError("Database has no table %s."%table_name)
1770
self.__connection__.execute('DELETE FROM ' + table_name)
1771
1772
def make_index(self, col_name, table_name, unique=False):
1773
"""
1774
Set the column ``col_name`` in table ``table_name`` to be an index,
1775
that is, a column set up to do quick searches on.
1776
1777
INPUT:
1778
1779
- ``col_name`` -- a string
1780
- ``table_name`` -- a string
1781
- ``unique`` -- requires that there are no multiple entries in the
1782
column, makes searching faster
1783
1784
EXAMPLES::
1785
1786
sage: MonicPolys = SQLDatabase()
1787
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
1788
sage: MonicPolys.make_index('n2','simon')
1789
sage: MonicPolys.get_skeleton()
1790
{'simon': {'n2': {'index': True, 'unique': False,
1791
'primary_key': False, 'sql': 'INTEGER'}, 'n': {'index': True,
1792
'unique': False, 'primary_key': False, 'sql': 'INTEGER'}}}
1793
"""
1794
if self.__read_only__:
1795
raise RuntimeError('Cannot modify a read only database.')
1796
if table_name not in self.__skeleton__:
1797
raise ValueError("Database has no table %s."%table_name)
1798
if col_name not in self.__skeleton__[table_name]:
1799
raise ValueError("Table %s has no column %s."%(table_name,col_name))
1800
1801
if unique:
1802
index_string = 'CREATE UNIQUE INDEX ' + col_name + ' ON ' \
1803
+ table_name + ' (' + col_name + ')'
1804
else:
1805
index_string = 'CREATE INDEX ' + col_name + ' ON ' + table_name \
1806
+ ' (' + col_name + ')'
1807
cur = self.__connection__.cursor()
1808
exe = cur.execute(index_string)
1809
1810
# Update Skeleton
1811
self.__skeleton__[table_name][col_name]['index'] = True
1812
if unique:
1813
self.__skeleton[table_name][col_name]['unique'] = True
1814
1815
def drop_index(self, table_name, index_name):
1816
"""
1817
Set the column ``index_name`` in table ``table_name`` to not be an
1818
index. See ``make_index()``
1819
1820
EXAMPLES::
1821
1822
sage: MonicPolys = SQLDatabase()
1823
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
1824
sage: MonicPolys.drop_index('simon', 'n')
1825
sage: MonicPolys.get_skeleton()
1826
{'simon': {'n2': {'index': False, 'unique': False,
1827
'primary_key': False, 'sql': 'INTEGER'}, 'n': {'index': False,
1828
'unique': False, 'primary_key': False, 'sql': 'INTEGER'}}}
1829
"""
1830
if self.__read_only__:
1831
raise RuntimeError('Cannot modify a read only database.')
1832
if table_name not in self.__skeleton__:
1833
raise ValueError("Database has no table %s."%table_name)
1834
if index_name not in self.__skeleton__[table_name]:
1835
raise ValueError("Table %s has no column %s."%(table,index_name))
1836
if not self.__skeleton__[table_name][index_name]['index']:
1837
return # silently
1838
1839
cur = self.__connection__.cursor()
1840
exe = cur.execute('DROP INDEX i_' + table_name + '_' + index_name)
1841
1842
# Update Skeleton
1843
self.__skeleton__[table_name][index_name]['index'] = False
1844
1845
def make_unique(self, table_name, col_name):
1846
"""
1847
Set the column ``col_name`` in table ``table_name`` to store unique
1848
values.
1849
1850
NOTE:
1851
1852
This function only adds the requirement for entries in ``col_name`` to
1853
be unique, it does not change the values.
1854
1855
EXAMPLES::
1856
1857
sage: MonicPolys = SQLDatabase()
1858
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
1859
sage: MonicPolys.make_unique('simon', 'n2')
1860
sage: MonicPolys.get_skeleton()
1861
{'simon': {'n2': {'index': False, 'unique': True,
1862
'primary_key': False, 'sql': 'INTEGER'}, 'n': {'index': True,
1863
'unique': False, 'primary_key': False, 'sql': 'INTEGER'}}}
1864
1865
"""
1866
if self.__read_only__:
1867
raise RuntimeError('Cannot modify a read only database')
1868
if table_name not in self.__skeleton__:
1869
raise ValueError("Database has no table %s."%table_name)
1870
if col_name not in self.__skeleton__[table_name]:
1871
raise ValueError("Table %s has no column %s."%(table_name, col_name))
1872
1873
# Update the skeleton:
1874
self.__skeleton__[table_name][col_name]['unique'] = True
1875
1876
self._rebuild_table(table_name)
1877
1878
def drop_unique(self, table_name, col_name):
1879
"""
1880
Set the column ``col_name`` in table ``table_name`` not store unique
1881
values.
1882
1883
NOTE:
1884
1885
This function only removes the requirement for entries in ``col_name``
1886
to be unique, it does not delete it.
1887
1888
EXAMPLES::
1889
1890
sage: MonicPolys = SQLDatabase()
1891
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
1892
sage: MonicPolys.make_unique('simon', 'n2')
1893
sage: MonicPolys.drop_unique('simon', 'n2')
1894
sage: MonicPolys.get_skeleton()
1895
{'simon': {'n2': {'index': False, 'unique': False,
1896
'primary_key': False, 'sql': 'INTEGER'}, 'n': {'index': True,
1897
'unique': False, 'primary_key': False, 'sql': 'INTEGER'}}}
1898
"""
1899
if self.__read_only__:
1900
raise RuntimeError('Cannot modify a read only database.')
1901
if table_name not in self.__skeleton__:
1902
raise ValueError("Database has no table %s."%table_name)
1903
if col_name not in self.__skeleton__[table_name]:
1904
raise ValueError("Table %s has no column %s."%(table_name, col_name))
1905
if self.__skeleton__[table_name][col_name]['primary_key']:
1906
raise ValueError("Primary keys must be unique.")
1907
1908
# Update the skeleton:
1909
self.__skeleton__[table_name][col_name]['unique'] = False
1910
1911
self._rebuild_table(table_name)
1912
1913
def make_primary_key(self, table_name, col_name):
1914
"""
1915
Set the column ``col_name`` in table ``table_name`` to be a primary key.
1916
1917
A primary key is something like an index, but its main purpose is to
1918
link different tables together. This allows searches to be executed on
1919
multiple tables that represent maybe different data about the same
1920
objects.
1921
1922
NOTE:
1923
1924
Some SQL features, such as automatically incrementing primary key,
1925
require the full word ``'INTEGER'``, not just ``'INT'``.
1926
1927
EXAMPLES::
1928
1929
sage: MonicPolys = SQLDatabase()
1930
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
1931
sage: MonicPolys.make_primary_key('simon', 'n2')
1932
sage: MonicPolys.get_skeleton()
1933
{'simon': {'n2': {'index': False, 'unique': True,
1934
'primary_key': True, 'sql': 'INTEGER'}, 'n': {'index': True,
1935
'unique': False, 'primary_key': False, 'sql': 'INTEGER'}}}
1936
"""
1937
if self.__read_only__:
1938
raise RuntimeError('Cannot modify a read only database.')
1939
if table_name not in self.__skeleton__:
1940
raise ValueError("Database has no table %s."%table_name)
1941
if col_name not in self.__skeleton__[table_name]:
1942
raise ValueError("Table %s has no column %s."%(table_name, col_name))
1943
1944
# Update the skeleton:
1945
self.__skeleton__[table_name][col_name]['primary_key'] = True
1946
self.__skeleton__[table_name][col_name]['unique'] = True
1947
1948
self._rebuild_table(table_name)
1949
1950
def drop_primary_key(self, table_name, col_name):
1951
"""
1952
Set the column ``col_name`` in table ``table_name`` not to be a primary
1953
key.
1954
1955
A primary key is something like an index, but its main purpose is to
1956
link different tables together. This allows searches to be executed on
1957
multiple tables that represent maybe different data about the same
1958
objects.
1959
1960
NOTE:
1961
1962
This function only changes the column to be non-primary, it does not
1963
delete it.
1964
1965
EXAMPLES::
1966
1967
sage: MonicPolys = SQLDatabase()
1968
sage: MonicPolys.create_table('simon', {'n':{'sql':'INTEGER', 'index':True}, 'n2':{'sql':'INTEGER'}})
1969
sage: MonicPolys.make_primary_key('simon', 'n2')
1970
sage: MonicPolys.drop_primary_key('simon', 'n2')
1971
sage: MonicPolys.get_skeleton()
1972
{'simon': {'n2': {'index': False, 'unique': True,
1973
'primary_key': False, 'sql': 'INTEGER'}, 'n': {'index': True,
1974
'unique': False, 'primary_key': False, 'sql': 'INTEGER'}}}
1975
"""
1976
if self.__read_only__:
1977
raise RuntimeError('Cannot modify a read only database.')
1978
if table_name not in self.__skeleton__:
1979
raise ValueError("Database has no table %s."%table_name)
1980
if col_name not in self.__skeleton__[table_name]:
1981
raise ValueError("Table %s has no column %s."%(table_name,col_name))
1982
if not self.__skeleton__[table_name][col_name]['primary_key']:
1983
return # silently
1984
1985
# Update the skeleton:
1986
self.__skeleton__[table_name][col_name]['primary_key'] = False
1987
1988
self._rebuild_table(table_name)
1989
1990
def add_row(self, table_name, values, entry_order=None):
1991
"""
1992
Add the row described by ``values`` to the table ``table_name``. Values
1993
should be a tuple, of same length and order as columns in given table.
1994
1995
NOTE:
1996
1997
If ``values`` is of length one, be sure to specify that it is a tuple of
1998
length one, by using a comma, e.g.::
1999
2000
sage: values = (6,)
2001
2002
EXAMPLES::
2003
2004
sage: DB = SQLDatabase()
2005
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
2006
sage: DB.add_row('simon',(0,1))
2007
sage: cur = DB.get_cursor()
2008
sage: (cur.execute('select * from simon')).fetchall()
2009
[(0, 1)]
2010
"""
2011
self.add_rows(table_name, [values], entry_order)
2012
2013
def delete_rows(self, query):
2014
"""
2015
Uses a ``SQLQuery`` instance to modify (delete rows from) the
2016
database.
2017
2018
``SQLQuery`` must have no join statements. (As of now, you can only
2019
delete from one table at a time -- ideas and patches are welcome).
2020
2021
To remove all data that satisfies a ``SQLQuery``, send the query as an
2022
argument to ``delete_rows``. Be careful, test your query first.
2023
2024
Recommended use: have some kind of row identification primary
2025
key column that you use as a parameter in the query. (See example
2026
below).
2027
2028
INPUT:
2029
2030
- ``query`` -- a ``SQLQuery`` (Delete the rows returned when query is
2031
run).
2032
2033
EXAMPLES::
2034
2035
sage: DB = SQLDatabase()
2036
sage: DB.create_table('lucy',{'id':{'sql':'INTEGER', 'primary_key':True, 'index':True}, 'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
2037
sage: DB.add_rows('lucy', [(0,1,1),(1,1,4),(2,0,7),(3,1,384), (4,1,978932)],['id','a1','b2'])
2038
sage: DB.show('lucy')
2039
a1 id b2
2040
------------------------------------------------------------
2041
1 0 1
2042
1 1 4
2043
0 2 7
2044
1 3 384
2045
1 4 978932
2046
sage: Q = SQLQuery(DB, {'table_name':'lucy', 'display_cols':['id','a1','b2'], 'expression':['id','>=',3]})
2047
sage: DB.delete_rows(Q)
2048
sage: DB.show('lucy')
2049
a1 id b2
2050
------------------------------------------------------------
2051
1 0 1
2052
1 1 4
2053
0 2 7
2054
"""
2055
if self.__read_only__:
2056
raise RuntimeError('Cannot delete rows from a read only database.')
2057
# Check query is associated with this database
2058
if not isinstance(query, SQLQuery):
2059
raise TypeError('%s is not a valid SQLQuery'%query)
2060
if query.__database__ is not self:
2061
raise ValueError('%s is not associated to this database.'%query)
2062
if (query.__query_string__).find(' JOIN ') != -1:
2063
raise ValueError('%s is not a valid query. Can only '%query \
2064
+ 'delete from one table at a time.')
2065
2066
delete_statement = re.sub('SELECT .* FROM', 'DELETE FROM', \
2067
query.__query_string__)
2068
2069
try:
2070
cur = self.get_cursor()
2071
cur.execute(delete_statement, query.__param_tuple__)
2072
except Exception:
2073
raise RuntimeError('Failure to complete delete. Check your data.')
2074
2075
def add_rows(self, table_name, rows, entry_order=None):
2076
"""
2077
INPUT:
2078
2079
- ``rows`` -- a list of tuples that represent one row of data to add
2080
(types should match col types in order)
2081
- ``entry_order`` -- an ordered list or tuple overrides normal order
2082
with user defined order
2083
2084
EXAMPLES::
2085
2086
sage: DB = SQLDatabase()
2087
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
2088
sage: DB.add_rows('simon',[(0,0),(1,1),(1,2)])
2089
sage: DB.add_rows('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
2090
sage: cur = DB.get_cursor()
2091
sage: (cur.execute('select * from simon')).fetchall()
2092
[(0, 0), (1, 1), (1, 2), (0, 0), (0, 4), (1, 5)]
2093
"""
2094
if self.__read_only__:
2095
raise RuntimeError('Cannot add rows to read only database.')
2096
quest = '(' + ', '.join('?' for i in rows[0]) + ')'
2097
strows = [tuple((str(entry) for entry in row)) for row in rows]
2098
2099
if entry_order is not None:
2100
self.__connection__.executemany('INSERT INTO ' + table_name \
2101
+ str(tuple(entry_order)) + ' VALUES ' + quest, strows)
2102
else:
2103
self.__connection__.executemany('INSERT INTO ' + table_name \
2104
+ ' VALUES ' + quest, strows)
2105
2106
add_data = add_rows
2107
2108
def vacuum(self):
2109
"""
2110
Cleans the extra hard disk space used up by a database that has
2111
recently shrunk.
2112
2113
EXAMPLES::
2114
2115
sage: DB = SQLDatabase()
2116
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
2117
sage: DB.add_row('simon',(0,1))
2118
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
2119
sage: DB.add_data('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
2120
sage: DB.drop_column('simon','b2')
2121
sage: DB.commit()
2122
sage: DB.vacuum()
2123
"""
2124
self.__connection__.execute('VACUUM')
2125
2126
def commit(self):
2127
"""
2128
Commits changes to file.
2129
2130
EXAMPLES::
2131
2132
sage: DB = SQLDatabase()
2133
sage: DB.create_table('simon',{'a1':{'sql':'bool'}, 'b2':{'sql':'int'}})
2134
sage: DB.add_row('simon',(0,1))
2135
sage: DB.add_data('simon',[(0,0),(1,1),(1,2)])
2136
sage: DB.add_data('simon',[(0,0),(4,0),(5,1)], ['b2','a1'])
2137
sage: DB.drop_column('simon','b2')
2138
sage: DB.commit()
2139
sage: DB.vacuum()
2140
"""
2141
if self.__read_only__:
2142
raise RuntimeError("Cannot commit read only database.")
2143
try:
2144
self.__connection__.execute('COMMIT')
2145
except sqlite.OperationalError:
2146
# Not sure why this throws an exception - but without it,
2147
# the changes are not committed so it is necessary.
2148
pass
2149
2150