forked from cirosantilli/python-cheat
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsqlite3_cheat.py
More file actions
executable file
·357 lines (282 loc) · 11.4 KB
/
sqlite3_cheat.py
File metadata and controls
executable file
·357 lines (282 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#!/usr/bin/env python
"""
## sqlite
https://docs.python.org/2/library/sqlite3.html
Oh, the Python stdlib is so overblown!
## Fetch methods
Fetch results that came from previous SELECT queries.
Results can only be fetched once, and then the next ones are fetched.
TODO example of multiple fetchone.
### commit
Saves to queries disk.
Beware: this slows things down a lot if you are doing bulk queries!
TODO ever needed for :memory:?
"""
import datetime
import os
import sqlite3
if 'Basic example':
db_path = 'sqlite.tmp'
data = [
('one', 1),
('two', 2)
]
data.sort()
# Create table and add some data.
connection = sqlite3.connect(db_path)
cursor = connection.cursor()
cursor.execute('DROP TABLE IF EXISTS t')
cursor.execute('CREATE TABLE t (t TEXT, i INT)')
cursor.executemany('INSERT INTO t VALUES (?, ?)', data)
connection.commit()
connection.close()
connection = sqlite3.connect(db_path)
cursor = connection.cursor()
cursor.execute('SELECT * FROM t ORDER BY t ASC')
result = cursor.fetchall()
# Strings are returned by default.
assert result == [('one', 1), ('two', 2)]
connection.close()
if '## row_factory':
"""
Parses row outputs into sqlite3.Row objects which are much more flexible.
In particular, this allows:
- map access to the row by column name: http://stackoverflow.com/questions/3300464/how-can-i-get-dict-from-sqlite-query
I recommend that you use this always.
"""
connection = sqlite3.connect(':memory:')
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
cursor.execute('CREATE TABLE t (i INT)')
cursor.execute('INSERT INTO t VALUES (?)', (1,))
cursor.execute('SELECT * FROM t')
row = cursor.fetchone()
assert type(row) is sqlite3.Row
assert row['i'] is 1
assert type(row['i']) is int
connection.close()
"""
# print(row) is different than print(str(row))
Either a horrible API choice, or a bug:
http://stackoverflow.com/questions/7920284/how-can-printing-an-object-result-in-different-output-than-both-str-and-repr
Consider instead:
print(row)
"""
#print row
#print(str(row))
if '## cursor':
"""
The second execute on a cursor erases previous results.
"""
connection = sqlite3.connect(':memory:')
cursor = connection.cursor()
cursor.execute('SELECT 1')
cursor.execute('SELECT 2')
assert cursor.fetchone()[0] is 2
assert cursor.fetchone() is None
connection.close()
if '## Query without cursor':
"""
sqlite3 has some convenience methods so that you
don't need to create an useless cursor for simple queries.
"""
connection = sqlite3.connect(':memory:')
assert next(connection.execute('SELECT 1'))[0] is 1
connection.close()
if '## NULL':
"""
Becomes None.
"""
connection = sqlite3.connect(':memory:')
cursor = connection.cursor()
cursor.execute('SELECT NULL')
row = cursor.fetchone()
assert row[0] is None
connection.close()
if '## execute on large tables':
"""
execute SELECT statements don' bring all the data to memory,
so it can be run on huge databases. TODO: how is that possible?
It does take a long time however, if you don't have an index.
There is of course no solution to that except creating the index.
"""
if '## DATE ## DATETIME ## TIMESTAMP':
"""
Comes out as strings by default.
http://stackoverflow.com/questions/1829872/how-to-read-datetime-back-from-sqlite-as-a-datetime-instead-of-string-in-python
"""
connection = sqlite3.connect(':memory:')
cursor = connection.cursor()
cursor.execute('CREATE TABLE t (c DATE, d DATETIME, e TIMESTAMP)')
today = datetime.date.today()
now = datetime.datetime.now()
cursor.execute('INSERT INTO t VALUES (?, ?, ?)', (today, now, now))
cursor.execute('SELECT * FROM t')
row = cursor.fetchone()
assert [type(x) for x in row] == [unicode] * 3
assert row == (unicode(today), unicode(now), unicode(now))
connection.close()
if '## PARSE_DECLTYPES':
connection = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
cursor = connection.cursor()
cursor.execute('CREATE TABLE t (c DATE, d DATETIME, e TIMESTAMP)')
today = datetime.date.today()
now = datetime.datetime.now()
cursor.execute('INSERT INTO t VALUES (?, ?, ?)', (today, now, now))
cursor.execute('SELECT * FROM t')
row = cursor.fetchone()
# TODO why only datetime comes out as a string?
assert [type(x) for x in row] == [datetime.date, unicode, datetime.datetime]
assert row == (today, unicode(now), now)
connection.close()
if '## Memory database #:memory:':
"""
A database with name :memory: only exists in memory.
Each connection creates a new separate database.
"""
connection1 = sqlite3.connect(':memory:')
connection2 = sqlite3.connect(':memory:')
cursor1 = connection1.cursor()
cursor2 = connection2.cursor()
cursor1.execute('CREATE TABLE t (i INT)')
connection1.commit()
# This works. Therefore `t` did not exist. So this is a separate DB.
cursor2.execute('CREATE TABLE t (i INT)')
connection2.commit()
connection1.close()
connection2.close()
"""
How to bootstrap in memory from DB file and write it back to the file afterwards:
- http://stackoverflow.com/questions/3850022/python-sqlite3-load-existing-db-file-to-memory
- http://stackoverflow.com/questions/31191727/moving-back-and-forth-between-an-on-disk-database-and-a-fast-in-memory-database?lq=1
"""
if '# Iterate search results':
"""
execute returns the cursor.
Cursors are iterable over the set of rows.
for row in c.execute('SELECT * FROM stocks ORDER BY price'):
print row
Can deal with large result sets, so it must be actually doing several queries in the backend?
"""
connection = sqlite3.connect(':memory:')
cursor = connection.cursor()
execute_return = cursor.execute('SELECT 1 UNION ALL SELECT 2')
assert type(execute_return) is sqlite3.Cursor
assert list(execute_return) == [(1,), (2,)]
connection.close()
if '# Transactions # Locking # timeout':
"""
- https://docs.python.org/2/library/sqlite3.html#sqlite3.connect
- https://docs.python.org/2/library/sqlite3.html#sqlite3-controlling-transactions
Interesting quote:
> When a database is accessed by multiple connections, and one of the processes modifies the database,
the SQLite database is locked until that transaction is committed.
The timeout parameter specifies how long the connection should wait
for the lock to go away until raising an exception.
The default for the timeout parameter is 5.0 (five seconds).
"""
if '# Locking error example with double insert':
connection1 = sqlite3.connect(db_path, timeout=0)
connection2 = sqlite3.connect(db_path, timeout=0)
cursor1 = connection1.cursor()
cursor2 = connection2.cursor()
cursor1.execute('DROP TABLE IF EXISTS t')
cursor1.execute('CREATE TABLE t (i INT)')
connection1.commit()
# Implicitly starts a transaction.
cursor1.execute('INSERT INTO t VALUES (1)')
try:
# Cannot start a new transaction while the other one is going on.
cursor2.execute('INSERT INTO t VALUES (1)')
except sqlite3.OperationalError:
pass
else:
raise
# Finish the transaction.
connection1.commit()
# This time it is fine, because we have already finished the previous one.
cursor2.execute('INSERT INTO t VALUES (1)')
connection1.close()
connection2.close()
if '# Locking error example with SELECT + INSERT':
connection1 = sqlite3.connect(db_path, timeout=0)
connection2 = sqlite3.connect(db_path, timeout=0)
cursor1 = connection1.cursor()
cursor2 = connection2.cursor()
cursor1.execute('DROP TABLE IF EXISTS t')
cursor1.execute('CREATE TABLE t (i INT)')
# If we comment this out and use an empty database, then no exception occurs. TODO Why?
cursor1.executemany('INSERT INTO t VALUES (?)', [(1,), (2,)])
connection1.commit()
# TODO why does this SELECT lead to the lock error?
cursor1.execute('SELECT * FROM t')
# TODO why does this make no difference? What should I do to fix things now?
connection1.commit()
cursor2.execute('INSERT INTO t VALUES (1)')
# TODO why do we blow up at the commit, instead of execute as in the INSERT INSERT example?
try:
connection2.commit()
except sqlite3.OperationalError:
pass
else:
raise
connection1.close()
connection2.close()
if 'iterate updating search results':
"""
This is an oversimplified example:
we could do this with a single update query of course.
TODO how?
- multiple connections gives: database is locked
- multiple cursors with a single connection messes things up in a way I don't understand
"""
# One connection and multiple cursors.
connection = sqlite3.connect(':memory:')
cursor1 = connection.cursor()
cursor2 = connection.cursor()
cursor1.execute('CREATE TABLE t (i INT)')
cursor1.executemany('INSERT INTO t VALUES (?)', [(1,), (2,)])
connection.commit()
for row in cursor1.execute('SELECT * FROM t'):
# On a real example, we do a very time consuming operation here.
cursor2.execute('UPDATE t SET i = ? WHERE i = ?', (row[0] + 10, row[0]))
# Because the operation is time consuming, we would like to commit here,
# before doing the time consuming operation many more times.
# Why can't we do this?
connection.commit()
# Committing here instead would works:
#connection.commit()
# TODO This assert should pass but fails. Why?
#assert list(cursor1.execute('SELECT * FROM t ORDER BY i ASC')) == [(11,), (12,)]
connection.close()
# Two connections.
connection1 = sqlite3.connect(db_path, timeout=0)
connection2 = sqlite3.connect(db_path, timeout=0)
cursor1 = connection1.cursor()
cursor2 = connection2.cursor()
cursor1.execute('DROP TABLE IF EXISTS t')
cursor1.execute('CREATE TABLE t (i INT)')
cursor1.executemany('INSERT INTO t VALUES (?)', [(1,), (2,)])
connection1.commit()
for row in cursor1.execute('SELECT * FROM t'):
cursor2.execute('UPDATE t SET i = ? WHERE i = ?', (row[0] + 10, row[0]))
# TODO Raises database is locked. I have minimized this elsewhere.
#connection2.commit()
#assert list(cursor1.execute('SELECT * FROM t ORDER BY i ASC')) == [(11,), (12,)]
connection1.close()
connection2.close()
# Get all the data out first.
connection = sqlite3.connect(db_path, timeout=0)
cursor = connection.cursor()
cursor.execute('DROP TABLE IF EXISTS t')
cursor.execute('CREATE TABLE t (i INT)')
cursor.executemany('INSERT INTO t VALUES (?)', [(1,), (2,)])
connection.commit()
cursor.execute('SELECT * FROM t')
rows = cursor.fetchall()
for row in rows:
cursor.execute('UPDATE t SET i = ? WHERE i = ?', (row[0] + 10, row[0]))
connection.commit()
assert list(cursor.execute('SELECT * FROM t ORDER BY i ASC')) == [(11,), (12,)]
connection.close()
connection.close()