sqlobject 3.13.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlobject/__init__.py +15 -0
- sqlobject/__version__.py +8 -0
- sqlobject/boundattributes.py +124 -0
- sqlobject/cache.py +376 -0
- sqlobject/classregistry.py +138 -0
- sqlobject/col.py +2187 -0
- sqlobject/compat.py +59 -0
- sqlobject/conftest.py +64 -0
- sqlobject/constraints.py +77 -0
- sqlobject/converters.py +278 -0
- sqlobject/dbconnection.py +1137 -0
- sqlobject/dberrors.py +54 -0
- sqlobject/declarative.py +211 -0
- sqlobject/events.py +365 -0
- sqlobject/firebird/__init__.py +8 -0
- sqlobject/firebird/firebirdconnection.py +463 -0
- sqlobject/include/__init__.py +1 -0
- sqlobject/include/hashcol.py +109 -0
- sqlobject/include/tests/__init__.py +1 -0
- sqlobject/include/tests/test_hashcol.py +84 -0
- sqlobject/index.py +189 -0
- sqlobject/inheritance/__init__.py +551 -0
- sqlobject/inheritance/iteration.py +106 -0
- sqlobject/inheritance/tests/__init__.py +1 -0
- sqlobject/inheritance/tests/test_aggregates.py +23 -0
- sqlobject/inheritance/tests/test_asdict.py +48 -0
- sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
- sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
- sqlobject/inheritance/tests/test_foreignKey.py +94 -0
- sqlobject/inheritance/tests/test_indexes.py +42 -0
- sqlobject/inheritance/tests/test_inheritance.py +164 -0
- sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
- sqlobject/joins.py +656 -0
- sqlobject/main.py +1880 -0
- sqlobject/manager/__init__.py +1 -0
- sqlobject/manager/command.py +1309 -0
- sqlobject/maxdb/__init__.py +8 -0
- sqlobject/maxdb/maxdbconnection.py +320 -0
- sqlobject/maxdb/readme.txt +27 -0
- sqlobject/mssql/__init__.py +8 -0
- sqlobject/mssql/mssqlconnection.py +417 -0
- sqlobject/mysql/__init__.py +8 -0
- sqlobject/mysql/mysqlconnection.py +562 -0
- sqlobject/postgres/__init__.py +8 -0
- sqlobject/postgres/pgconnection.py +619 -0
- sqlobject/sqlbuilder.py +1655 -0
- sqlobject/sqlite/__init__.py +8 -0
- sqlobject/sqlite/sqliteconnection.py +418 -0
- sqlobject/sresults.py +377 -0
- sqlobject/styles.py +174 -0
- sqlobject/sybase/__init__.py +8 -0
- sqlobject/sybase/sybaseconnection.py +183 -0
- sqlobject/tests/__init__.py +1 -0
- sqlobject/tests/dbtest.py +363 -0
- sqlobject/tests/test_ForeignKey.py +133 -0
- sqlobject/tests/test_ForeignKey_cascade.py +150 -0
- sqlobject/tests/test_NoneValuedResultItem.py +33 -0
- sqlobject/tests/test_SQLMultipleJoin.py +65 -0
- sqlobject/tests/test_SQLRelatedJoin.py +114 -0
- sqlobject/tests/test_SingleJoin.py +30 -0
- sqlobject/tests/test_aggregates.py +75 -0
- sqlobject/tests/test_aliases.py +49 -0
- sqlobject/tests/test_asdict.py +18 -0
- sqlobject/tests/test_auto.py +218 -0
- sqlobject/tests/test_basic.py +359 -0
- sqlobject/tests/test_blob.py +34 -0
- sqlobject/tests/test_boundattributes.py +67 -0
- sqlobject/tests/test_cache.py +57 -0
- sqlobject/tests/test_class_hash.py +22 -0
- sqlobject/tests/test_columns_order.py +18 -0
- sqlobject/tests/test_combining_joins.py +47 -0
- sqlobject/tests/test_comparison.py +24 -0
- sqlobject/tests/test_compat.py +10 -0
- sqlobject/tests/test_complex_sorting.py +77 -0
- sqlobject/tests/test_constraints.py +43 -0
- sqlobject/tests/test_converters.py +276 -0
- sqlobject/tests/test_create_drop.py +29 -0
- sqlobject/tests/test_csvexport.py +101 -0
- sqlobject/tests/test_csvimport.py +21 -0
- sqlobject/tests/test_cyclic_reference.py +55 -0
- sqlobject/tests/test_datetime.py +171 -0
- sqlobject/tests/test_dbconnection.py +17 -0
- sqlobject/tests/test_decimal.py +63 -0
- sqlobject/tests/test_declarative.py +83 -0
- sqlobject/tests/test_default_style.py +65 -0
- sqlobject/tests/test_delete.py +74 -0
- sqlobject/tests/test_distinct.py +36 -0
- sqlobject/tests/test_empty.py +22 -0
- sqlobject/tests/test_enum.py +75 -0
- sqlobject/tests/test_events.py +142 -0
- sqlobject/tests/test_exceptions.py +40 -0
- sqlobject/tests/test_expire.py +29 -0
- sqlobject/tests/test_groupBy.py +45 -0
- sqlobject/tests/test_identity.py +29 -0
- sqlobject/tests/test_indexes.py +100 -0
- sqlobject/tests/test_inheritance.py +35 -0
- sqlobject/tests/test_joins.py +160 -0
- sqlobject/tests/test_joins_conditional.py +143 -0
- sqlobject/tests/test_jsonbcol.py +53 -0
- sqlobject/tests/test_jsoncol.py +56 -0
- sqlobject/tests/test_lazy.py +165 -0
- sqlobject/tests/test_md5.py +13 -0
- sqlobject/tests/test_mysql.py +50 -0
- sqlobject/tests/test_new_joins.py +165 -0
- sqlobject/tests/test_parse_uri.py +147 -0
- sqlobject/tests/test_paste.py +92 -0
- sqlobject/tests/test_perConnection.py +20 -0
- sqlobject/tests/test_pickle.py +42 -0
- sqlobject/tests/test_picklecol.py +44 -0
- sqlobject/tests/test_postgres.py +82 -0
- sqlobject/tests/test_reparent_sqlmeta.py +33 -0
- sqlobject/tests/test_schema.py +30 -0
- sqlobject/tests/test_select.py +218 -0
- sqlobject/tests/test_select_through.py +63 -0
- sqlobject/tests/test_setters.py +23 -0
- sqlobject/tests/test_slice.py +59 -0
- sqlobject/tests/test_sorting.py +80 -0
- sqlobject/tests/test_sqlbuilder.py +134 -0
- sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
- sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
- sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
- sqlobject/tests/test_sqlite.py +160 -0
- sqlobject/tests/test_sqlmeta_idName.py +52 -0
- sqlobject/tests/test_sqlobject_admin.py +38 -0
- sqlobject/tests/test_string_id.py +79 -0
- sqlobject/tests/test_style.py +36 -0
- sqlobject/tests/test_subqueries.py +142 -0
- sqlobject/tests/test_transactions.py +122 -0
- sqlobject/tests/test_unicode.py +84 -0
- sqlobject/tests/test_uuidcol.py +40 -0
- sqlobject/tests/test_validation.py +125 -0
- sqlobject/tests/test_views.py +159 -0
- sqlobject/util/__init__.py +1 -0
- sqlobject/util/csvexport.py +210 -0
- sqlobject/util/csvimport.py +366 -0
- sqlobject/util/moduleloader.py +35 -0
- sqlobject/util/threadinglocal.py +6 -0
- sqlobject/versioning/__init__.py +120 -0
- sqlobject/versioning/test/__init__.py +0 -0
- sqlobject/versioning/test/test_version.py +183 -0
- sqlobject/views.py +151 -0
- sqlobject/wsgi_middleware.py +109 -0
- sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
- sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
- sqlobject-3.13.1.dist-info/METADATA +177 -0
- sqlobject-3.13.1.dist-info/RECORD +149 -0
- sqlobject-3.13.1.dist-info/WHEEL +6 -0
- sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
- sqlobject-3.13.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
try:
|
|
4
|
+
from _thread import get_ident
|
|
5
|
+
except ImportError:
|
|
6
|
+
from thread import get_ident
|
|
7
|
+
from threading import enumerate as enumerate_threads
|
|
8
|
+
try:
|
|
9
|
+
from urllib import quote
|
|
10
|
+
except ImportError:
|
|
11
|
+
from urllib.parse import quote
|
|
12
|
+
from sqlobject import col
|
|
13
|
+
from sqlobject import dberrors
|
|
14
|
+
from sqlobject.dbconnection import DBAPI, Boolean
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
sqlite2_Binary = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ErrorMessage(str):
|
|
21
|
+
def __new__(cls, e):
|
|
22
|
+
obj = str.__new__(cls, e.args[0])
|
|
23
|
+
obj.code = None
|
|
24
|
+
obj.module = e.__module__
|
|
25
|
+
obj.exception = e.__class__.__name__
|
|
26
|
+
return obj
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SQLiteConnection(DBAPI):
|
|
30
|
+
|
|
31
|
+
supportTransactions = True
|
|
32
|
+
dbName = 'sqlite'
|
|
33
|
+
schemes = [dbName]
|
|
34
|
+
|
|
35
|
+
def __init__(self, filename, autoCommit=1, **kw):
|
|
36
|
+
import sqlite3 as sqlite
|
|
37
|
+
sqlite.encode = base64.b64encode
|
|
38
|
+
sqlite.decode = base64.b64decode
|
|
39
|
+
self.module = sqlite
|
|
40
|
+
self.filename = filename # full path to sqlite-db-file
|
|
41
|
+
self._memory = filename == ':memory:'
|
|
42
|
+
# connection options
|
|
43
|
+
opts = {}
|
|
44
|
+
if autoCommit:
|
|
45
|
+
opts["isolation_level"] = None
|
|
46
|
+
global sqlite2_Binary
|
|
47
|
+
if sqlite2_Binary is None:
|
|
48
|
+
sqlite2_Binary = sqlite.Binary
|
|
49
|
+
sqlite.Binary = lambda s: sqlite2_Binary(sqlite.encode(s))
|
|
50
|
+
if 'factory' in kw:
|
|
51
|
+
factory = kw.pop('factory')
|
|
52
|
+
if isinstance(factory, str):
|
|
53
|
+
factory = globals()[factory]
|
|
54
|
+
opts['factory'] = factory(sqlite)
|
|
55
|
+
if 'timeout' in kw:
|
|
56
|
+
opts['timeout'] = float(kw.pop('timeout'))
|
|
57
|
+
if 'check_same_thread' in kw:
|
|
58
|
+
opts["check_same_thread"] = Boolean(kw.pop('check_same_thread'))
|
|
59
|
+
# use only one connection for sqlite - supports multiple)
|
|
60
|
+
# cursors per connection
|
|
61
|
+
self._connOptions = opts
|
|
62
|
+
self.use_table_info = Boolean(kw.pop("use_table_info", True))
|
|
63
|
+
DBAPI.__init__(self, **kw)
|
|
64
|
+
self._threadPool = {}
|
|
65
|
+
self._threadOrigination = {}
|
|
66
|
+
if self._memory:
|
|
67
|
+
self.makeMemoryConnection()
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def _connectionFromParams(cls, user, password, host, port, path, args):
|
|
71
|
+
assert host is None and port is None, (
|
|
72
|
+
"SQLite can only be used locally (with a URI like "
|
|
73
|
+
"sqlite:/file or sqlite:///file, not sqlite://%s%s)" %
|
|
74
|
+
(host, port and ':%r' % port or ''))
|
|
75
|
+
assert user is None and password is None, (
|
|
76
|
+
"You may not provide usernames or passwords for SQLite "
|
|
77
|
+
"databases")
|
|
78
|
+
if path == "/:memory:":
|
|
79
|
+
path = ":memory:"
|
|
80
|
+
return cls(filename=path, **args)
|
|
81
|
+
|
|
82
|
+
def oldUri(self):
|
|
83
|
+
path = self.filename
|
|
84
|
+
if path == ":memory:":
|
|
85
|
+
path = "/:memory:"
|
|
86
|
+
else:
|
|
87
|
+
path = "//" + path
|
|
88
|
+
return 'sqlite:%s' % path
|
|
89
|
+
|
|
90
|
+
def uri(self):
|
|
91
|
+
path = self.filename
|
|
92
|
+
if path == ":memory:":
|
|
93
|
+
path = "/:memory:"
|
|
94
|
+
else:
|
|
95
|
+
if path.startswith('/'):
|
|
96
|
+
path = "//" + path
|
|
97
|
+
else:
|
|
98
|
+
path = "///" + path
|
|
99
|
+
path = quote(path)
|
|
100
|
+
return 'sqlite:%s' % path
|
|
101
|
+
|
|
102
|
+
def getConnection(self):
|
|
103
|
+
# SQLite can't share connections between threads, and so can't
|
|
104
|
+
# pool connections. Since we are isolating threads here, we
|
|
105
|
+
# don't have to worry about locking as much.
|
|
106
|
+
if self._memory:
|
|
107
|
+
conn = self.makeConnection()
|
|
108
|
+
self._connectionNumbers[id(conn)] = self._connectionCount
|
|
109
|
+
self._connectionCount += 1
|
|
110
|
+
return conn
|
|
111
|
+
self._releaseUnusedConnections()
|
|
112
|
+
threadid = get_ident()
|
|
113
|
+
if (self._pool is not None and threadid in self._threadPool):
|
|
114
|
+
conn = self._threadPool[threadid]
|
|
115
|
+
del self._threadPool[threadid]
|
|
116
|
+
if conn in self._pool:
|
|
117
|
+
self._pool.remove(conn)
|
|
118
|
+
else:
|
|
119
|
+
conn = self.makeConnection()
|
|
120
|
+
if self._pool is not None:
|
|
121
|
+
self._threadOrigination[id(conn)] = threadid
|
|
122
|
+
self._connectionNumbers[id(conn)] = self._connectionCount
|
|
123
|
+
self._connectionCount += 1
|
|
124
|
+
if self.debug:
|
|
125
|
+
s = 'ACQUIRE'
|
|
126
|
+
if self._pool is not None:
|
|
127
|
+
s += ' pool=[%s]' % ', '.join(
|
|
128
|
+
[str(self._connectionNumbers[id(v)]) for v in self._pool])
|
|
129
|
+
self.printDebug(conn, s, 'Pool')
|
|
130
|
+
return conn
|
|
131
|
+
|
|
132
|
+
def releaseConnection(self, conn, explicit=False):
|
|
133
|
+
if self._memory:
|
|
134
|
+
return
|
|
135
|
+
threadid = self._threadOrigination.get(id(conn))
|
|
136
|
+
DBAPI.releaseConnection(self, conn, explicit=explicit)
|
|
137
|
+
if (self._pool is not None and threadid
|
|
138
|
+
and threadid not in self._threadPool):
|
|
139
|
+
self._threadPool[threadid] = conn
|
|
140
|
+
else:
|
|
141
|
+
if self._pool and conn in self._pool:
|
|
142
|
+
self._pool.remove(conn)
|
|
143
|
+
if threadid:
|
|
144
|
+
del self._threadOrigination[id(conn)]
|
|
145
|
+
del self._threadPool[threadid]
|
|
146
|
+
conn.close()
|
|
147
|
+
|
|
148
|
+
def _setAutoCommit(self, conn, auto):
|
|
149
|
+
if auto:
|
|
150
|
+
conn.isolation_level = None
|
|
151
|
+
else:
|
|
152
|
+
conn.isolation_level = ""
|
|
153
|
+
|
|
154
|
+
def _setIsolationLevel(self, conn, level):
|
|
155
|
+
conn.isolation_level = level
|
|
156
|
+
|
|
157
|
+
def makeMemoryConnection(self):
|
|
158
|
+
self._memoryConn = self.module.connect(
|
|
159
|
+
self.filename, **self._connOptions)
|
|
160
|
+
# Convert text data from SQLite to str, not unicode -
|
|
161
|
+
# SQLObject converts it to unicode itself.
|
|
162
|
+
self._memoryConn.text_factory = str
|
|
163
|
+
|
|
164
|
+
def makeConnection(self):
|
|
165
|
+
if self._memory:
|
|
166
|
+
return self._memoryConn
|
|
167
|
+
conn = self.module.connect(self.filename, **self._connOptions)
|
|
168
|
+
conn.text_factory = str # Convert text data to str, not unicode
|
|
169
|
+
return conn
|
|
170
|
+
|
|
171
|
+
def _releaseUnusedConnections(self):
|
|
172
|
+
"""Release connections from threads that're no longer active"""
|
|
173
|
+
thread_ids = set(t.ident for t in enumerate_threads())
|
|
174
|
+
tp_set = set(self._threadPool)
|
|
175
|
+
unused_connections = [
|
|
176
|
+
self._threadPool[tid] for tid in tp_set - thread_ids
|
|
177
|
+
]
|
|
178
|
+
for unused_connection in unused_connections:
|
|
179
|
+
try:
|
|
180
|
+
self.releaseConnection(unused_connection, explicit=True)
|
|
181
|
+
except self.module.ProgrammingError:
|
|
182
|
+
pass # Ignore error in `conn.close()` from a different thread
|
|
183
|
+
|
|
184
|
+
def close(self):
|
|
185
|
+
DBAPI.close(self)
|
|
186
|
+
self._threadPool = {}
|
|
187
|
+
self._threadOrigination = {}
|
|
188
|
+
if self._memory:
|
|
189
|
+
self._memoryConn.close()
|
|
190
|
+
self.makeMemoryConnection()
|
|
191
|
+
|
|
192
|
+
def _executeRetry(self, conn, cursor, query):
|
|
193
|
+
if self.debug:
|
|
194
|
+
self.printDebug(conn, query, 'QueryR')
|
|
195
|
+
try:
|
|
196
|
+
return cursor.execute(query)
|
|
197
|
+
except self.module.OperationalError as e:
|
|
198
|
+
raise dberrors.OperationalError(ErrorMessage(e))
|
|
199
|
+
except self.module.IntegrityError as e:
|
|
200
|
+
msg = ErrorMessage(e)
|
|
201
|
+
if msg.startswith('column') and msg.endswith('not unique') \
|
|
202
|
+
or msg.startswith('UNIQUE constraint failed:'):
|
|
203
|
+
raise dberrors.DuplicateEntryError(msg)
|
|
204
|
+
else:
|
|
205
|
+
raise dberrors.IntegrityError(msg)
|
|
206
|
+
except self.module.InternalError as e:
|
|
207
|
+
raise dberrors.InternalError(ErrorMessage(e))
|
|
208
|
+
except self.module.ProgrammingError as e:
|
|
209
|
+
raise dberrors.ProgrammingError(ErrorMessage(e))
|
|
210
|
+
except self.module.DataError as e:
|
|
211
|
+
raise dberrors.DataError(ErrorMessage(e))
|
|
212
|
+
except self.module.NotSupportedError as e:
|
|
213
|
+
raise dberrors.NotSupportedError(ErrorMessage(e))
|
|
214
|
+
except self.module.DatabaseError as e:
|
|
215
|
+
raise dberrors.DatabaseError(ErrorMessage(e))
|
|
216
|
+
except self.module.InterfaceError as e:
|
|
217
|
+
raise dberrors.InterfaceError(ErrorMessage(e))
|
|
218
|
+
except self.module.Warning as e:
|
|
219
|
+
raise Warning(ErrorMessage(e))
|
|
220
|
+
except self.module.Error as e:
|
|
221
|
+
raise dberrors.Error(ErrorMessage(e))
|
|
222
|
+
|
|
223
|
+
def _queryInsertID(self, conn, soInstance, id, names, values):
|
|
224
|
+
table = soInstance.sqlmeta.table
|
|
225
|
+
idName = soInstance.sqlmeta.idName
|
|
226
|
+
c = conn.cursor()
|
|
227
|
+
if id is not None:
|
|
228
|
+
names = [idName] + names
|
|
229
|
+
values = [id] + values
|
|
230
|
+
q = self._insertSQL(table, names, values)
|
|
231
|
+
if self.debug:
|
|
232
|
+
self.printDebug(conn, q, 'QueryIns')
|
|
233
|
+
self._executeRetry(conn, c, q)
|
|
234
|
+
# lastrowid is a DB-API extension from "PEP 0249":
|
|
235
|
+
if id is None:
|
|
236
|
+
id = int(c.lastrowid)
|
|
237
|
+
c.close()
|
|
238
|
+
if self.debugOutput:
|
|
239
|
+
self.printDebug(conn, id, 'QueryIns', 'result')
|
|
240
|
+
return id
|
|
241
|
+
|
|
242
|
+
def _insertSQL(self, table, names, values):
|
|
243
|
+
if not names:
|
|
244
|
+
assert not values
|
|
245
|
+
# INSERT INTO table () VALUES () isn't allowed in
|
|
246
|
+
# SQLite (though it is in other databases)
|
|
247
|
+
return ("INSERT INTO %s VALUES (NULL)" % table)
|
|
248
|
+
else:
|
|
249
|
+
return DBAPI._insertSQL(self, table, names, values)
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def _queryAddLimitOffset(cls, query, start, end):
|
|
253
|
+
if not start:
|
|
254
|
+
return "%s LIMIT %i" % (query, end)
|
|
255
|
+
if not end:
|
|
256
|
+
return "%s LIMIT 0 OFFSET %i" % (query, start)
|
|
257
|
+
return "%s LIMIT %i OFFSET %i" % (query, end - start, start)
|
|
258
|
+
|
|
259
|
+
def createColumn(self, soClass, col):
|
|
260
|
+
return col.sqliteCreateSQL()
|
|
261
|
+
|
|
262
|
+
def createReferenceConstraint(self, soClass, col):
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def createIDColumn(self, soClass):
|
|
266
|
+
return self._createIDColumn(soClass.sqlmeta)
|
|
267
|
+
|
|
268
|
+
def _createIDColumn(self, sqlmeta):
|
|
269
|
+
if sqlmeta.idType is str:
|
|
270
|
+
return '%s TEXT PRIMARY KEY' % sqlmeta.idName
|
|
271
|
+
if sqlmeta.idType is not int:
|
|
272
|
+
raise TypeError('sqlmeta.idType must be int or str, not %r'
|
|
273
|
+
% sqlmeta.idType)
|
|
274
|
+
return '%s INTEGER PRIMARY KEY AUTOINCREMENT' % sqlmeta.idName
|
|
275
|
+
|
|
276
|
+
def joinSQLType(self, join):
|
|
277
|
+
return 'INT NOT NULL'
|
|
278
|
+
|
|
279
|
+
def tableExists(self, tableName):
|
|
280
|
+
result = self.queryOne(
|
|
281
|
+
"SELECT tbl_name FROM sqlite_master "
|
|
282
|
+
"WHERE type='table' AND tbl_name = '%s'" % tableName)
|
|
283
|
+
# turn it into a boolean:
|
|
284
|
+
return not not result
|
|
285
|
+
|
|
286
|
+
def createIndexSQL(self, soClass, index):
|
|
287
|
+
return index.sqliteCreateIndexSQL(soClass)
|
|
288
|
+
|
|
289
|
+
def addColumn(self, tableName, column):
|
|
290
|
+
self.query('ALTER TABLE %s ADD COLUMN %s' %
|
|
291
|
+
(tableName,
|
|
292
|
+
column.sqliteCreateSQL()))
|
|
293
|
+
self.query('VACUUM')
|
|
294
|
+
|
|
295
|
+
def delColumn(self, sqlmeta, column):
|
|
296
|
+
self.recreateTableWithoutColumn(sqlmeta, column)
|
|
297
|
+
|
|
298
|
+
def recreateTableWithoutColumn(self, sqlmeta, column):
|
|
299
|
+
new_name = sqlmeta.table + '_ORIGINAL'
|
|
300
|
+
self.query('ALTER TABLE %s RENAME TO %s' % (sqlmeta.table, new_name))
|
|
301
|
+
cols = [self._createIDColumn(sqlmeta)] + \
|
|
302
|
+
[self.createColumn(None, col)
|
|
303
|
+
for col in sqlmeta.columnList if col.name != column.name]
|
|
304
|
+
cols = ",\n".join([" %s" % c for c in cols])
|
|
305
|
+
self.query('CREATE TABLE %s (\n%s\n)' % (sqlmeta.table, cols))
|
|
306
|
+
all_columns = ', '.join(
|
|
307
|
+
[sqlmeta.idName] + [col.dbName for col in sqlmeta.columnList])
|
|
308
|
+
self.query('INSERT INTO %s (%s) SELECT %s FROM %s' % (
|
|
309
|
+
sqlmeta.table, all_columns, all_columns, new_name))
|
|
310
|
+
self.query('DROP TABLE %s' % new_name)
|
|
311
|
+
|
|
312
|
+
def columnsFromSchema(self, tableName, soClass):
|
|
313
|
+
if self.use_table_info:
|
|
314
|
+
return self._columnsFromSchemaTableInfo(tableName, soClass)
|
|
315
|
+
else:
|
|
316
|
+
return self._columnsFromSchemaParse(tableName, soClass)
|
|
317
|
+
|
|
318
|
+
def _columnsFromSchemaTableInfo(self, tableName, soClass):
|
|
319
|
+
colData = self.queryAll("PRAGMA table_info(%s)" % tableName)
|
|
320
|
+
results = []
|
|
321
|
+
for index, field, t, nullAllowed, default, key in colData:
|
|
322
|
+
if field == soClass.sqlmeta.idName:
|
|
323
|
+
continue
|
|
324
|
+
colClass, kw = self.guessClass(t)
|
|
325
|
+
if default == 'NULL':
|
|
326
|
+
nullAllowed = True
|
|
327
|
+
default = None
|
|
328
|
+
kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
|
|
329
|
+
kw['dbName'] = field
|
|
330
|
+
kw['notNone'] = not nullAllowed
|
|
331
|
+
kw['default'] = default
|
|
332
|
+
# @@ skip key...
|
|
333
|
+
# @@ skip extra...
|
|
334
|
+
results.append(colClass(**kw))
|
|
335
|
+
return results
|
|
336
|
+
|
|
337
|
+
def _columnsFromSchemaParse(self, tableName, soClass):
|
|
338
|
+
colData = self.queryOne(
|
|
339
|
+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" %
|
|
340
|
+
tableName)
|
|
341
|
+
if not colData:
|
|
342
|
+
raise ValueError(
|
|
343
|
+
'The table %s was not found in the database. Load failed.' %
|
|
344
|
+
tableName)
|
|
345
|
+
colData = colData[0].split('(', 1)[1].strip()[:-2]
|
|
346
|
+
while True:
|
|
347
|
+
start = colData.find('(')
|
|
348
|
+
if start == -1:
|
|
349
|
+
break
|
|
350
|
+
end = colData.find(')', start)
|
|
351
|
+
if end == -1:
|
|
352
|
+
break
|
|
353
|
+
colData = colData[:start] + colData[end + 1:]
|
|
354
|
+
results = []
|
|
355
|
+
for colDesc in colData.split(','):
|
|
356
|
+
parts = colDesc.strip().split(' ', 2)
|
|
357
|
+
field = parts[0].strip()
|
|
358
|
+
# skip comments
|
|
359
|
+
if field.startswith('--'):
|
|
360
|
+
continue
|
|
361
|
+
# get rid of enclosing quotes
|
|
362
|
+
if field[0] == field[-1] == '"':
|
|
363
|
+
field = field[1:-1]
|
|
364
|
+
if field == getattr(soClass.sqlmeta, 'idName', 'id'):
|
|
365
|
+
continue
|
|
366
|
+
colClass, kw = self.guessClass(parts[1].strip())
|
|
367
|
+
if len(parts) == 2:
|
|
368
|
+
index_info = ''
|
|
369
|
+
else:
|
|
370
|
+
index_info = parts[2].strip().upper()
|
|
371
|
+
kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
|
|
372
|
+
kw['dbName'] = field
|
|
373
|
+
import re
|
|
374
|
+
nullble = re.search(r'(\b\S*)\sNULL', index_info)
|
|
375
|
+
default = re.search(
|
|
376
|
+
r"DEFAULT\s((?:\d[\dA-FX.]*)|(?:'[^']*')|(?:#[^#]*#))",
|
|
377
|
+
index_info)
|
|
378
|
+
kw['notNone'] = nullble and nullble.group(1) == 'NOT'
|
|
379
|
+
kw['default'] = default and default.group(1)
|
|
380
|
+
# @@ skip key...
|
|
381
|
+
# @@ skip extra...
|
|
382
|
+
results.append(colClass(**kw))
|
|
383
|
+
return results
|
|
384
|
+
|
|
385
|
+
def guessClass(self, t):
|
|
386
|
+
t = t.upper()
|
|
387
|
+
if t.find('INT') >= 0:
|
|
388
|
+
return col.IntCol, {}
|
|
389
|
+
elif t.find('TEXT') >= 0 or t.find('CHAR') >= 0 or t.find('CLOB') >= 0:
|
|
390
|
+
return col.StringCol, {'length': 2 ** 32 - 1}
|
|
391
|
+
elif t.find('BLOB') >= 0:
|
|
392
|
+
return col.BLOBCol, {"length": 2 ** 32 - 1}
|
|
393
|
+
elif t.find('REAL') >= 0 or t.find('FLOAT') >= 0:
|
|
394
|
+
return col.FloatCol, {}
|
|
395
|
+
elif t.find('DECIMAL') >= 0:
|
|
396
|
+
return col.DecimalCol, {'size': None, 'precision': None}
|
|
397
|
+
elif t.find('BOOL') >= 0:
|
|
398
|
+
return col.BoolCol, {}
|
|
399
|
+
else:
|
|
400
|
+
return col.Col, {}
|
|
401
|
+
|
|
402
|
+
def listTables(self):
|
|
403
|
+
return [v[0] for v in self.queryAll(
|
|
404
|
+
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")]
|
|
405
|
+
|
|
406
|
+
def listDatabases(self):
|
|
407
|
+
# The pragma returns a list of (index, name, filename)
|
|
408
|
+
return [v[1] for v in self.queryAll("PRAGMA database_list")]
|
|
409
|
+
|
|
410
|
+
def createEmptyDatabase(self):
|
|
411
|
+
if self._memory:
|
|
412
|
+
return
|
|
413
|
+
open(self.filename, 'w').close()
|
|
414
|
+
|
|
415
|
+
def dropDatabase(self):
|
|
416
|
+
if self._memory:
|
|
417
|
+
return
|
|
418
|
+
os.unlink(self.filename)
|