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