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