diff options
Diffstat (limited to 'django/db/backends/sqlite3')
| -rw-r--r-- | django/db/backends/sqlite3/__init__.py | 0 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/base.py | 150 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/client.py | 6 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/creation.py | 29 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/introspection.py | 50 |
5 files changed, 235 insertions, 0 deletions
diff --git a/django/db/backends/sqlite3/__init__.py b/django/db/backends/sqlite3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/django/db/backends/sqlite3/__init__.py diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py new file mode 100644 index 0000000000..ecaf9b0c0d --- /dev/null +++ b/django/db/backends/sqlite3/base.py @@ -0,0 +1,150 @@ +""" +SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/). +""" + +from django.db.backends import util +from pysqlite2 import dbapi2 as Database + +DatabaseError = Database.DatabaseError + +Database.register_converter("bool", lambda s: str(s) == '1') +Database.register_converter("time", util.typecast_time) +Database.register_converter("date", util.typecast_date) +Database.register_converter("datetime", util.typecast_timestamp) + +def utf8rowFactory(cursor, row): + def utf8(s): + if type(s) == unicode: + return s.encode("utf-8") + else: + return s + return [utf8(r) for r in row] + +try: + # Only exists in Python 2.4+ + from threading import local +except ImportError: + # Import copy of _thread_local.py from Python 2.4 + from django.utils._threading_local import local + +class DatabaseWrapper(local): + def __init__(self): + self.connection = None + self.queries = [] + + def cursor(self): + from django.conf import settings + if self.connection is None: + self.connection = Database.connect(settings.DATABASE_NAME, detect_types=Database.PARSE_DECLTYPES) + # register extract and date_trun functions + self.connection.create_function("django_extract", 2, _sqlite_extract) + self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) + cursor = self.connection.cursor(factory=SQLiteCursorWrapper) + cursor.row_factory = utf8rowFactory + if settings.DEBUG: + return util.CursorDebugWrapper(cursor, self) + else: + return cursor + + def _commit(self): + self.connection.commit() + + def _rollback(self): + if self.connection: + self.connection.rollback() + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + +class SQLiteCursorWrapper(Database.Cursor): + """ + Django uses "format" style placeholders, but pysqlite2 uses "qmark" style. + This fixes it -- but note that if you want to use a literal "%s" in a query, + you'll need to use "%%s". + """ + def execute(self, query, params=()): + query = self.convert_query(query, len(params)) + return Database.Cursor.execute(self, query, params) + + def executemany(self, query, param_list): + query = self.convert_query(query, len(param_list[0])) + return Database.Cursor.executemany(self, query, param_list) + + def convert_query(self, query, num_params): + return query % tuple("?" * num_params) + +supports_constraints = False + +def quote_name(name): + if name.startswith('"') and name.endswith('"'): + return name # Quoting once is enough. + return '"%s"' % name + +dictfetchone = util.dictfetchone +dictfetchmany = util.dictfetchmany +dictfetchall = util.dictfetchall + +def get_last_insert_id(cursor, table_name, pk_name): + return cursor.lastrowid + +def get_date_extract_sql(lookup_type, table_name): + # lookup_type is 'year', 'month', 'day' + # sqlite doesn't support extract, so we fake it with the user-defined + # function _sqlite_extract that's registered in connect(), above. + return 'django_extract("%s", %s)' % (lookup_type.lower(), table_name) + +def _sqlite_extract(lookup_type, dt): + try: + dt = util.typecast_timestamp(dt) + except (ValueError, TypeError): + return None + return str(getattr(dt, lookup_type)) + +def get_date_trunc_sql(lookup_type, field_name): + # lookup_type is 'year', 'month', 'day' + # sqlite doesn't support DATE_TRUNC, so we fake it as above. + return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name) + +def get_limit_offset_sql(limit, offset=None): + sql = "LIMIT %s" % limit + if offset and offset != 0: + sql += " OFFSET %s" % offset + return sql + +def get_random_function_sql(): + return "RANDOM()" + +def get_drop_foreignkey_sql(): + return "" + +def _sqlite_date_trunc(lookup_type, dt): + try: + dt = util.typecast_timestamp(dt) + except (ValueError, TypeError): + return None + if lookup_type == 'year': + return "%i-01-01 00:00:00" % dt.year + elif lookup_type == 'month': + return "%i-%02i-01 00:00:00" % (dt.year, dt.month) + elif lookup_type == 'day': + return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day) + +# SQLite requires LIKE statements to include an ESCAPE clause if the value +# being escaped has a percent or underscore in it. +# See http://www.sqlite.org/lang_expr.html for an explanation. +OPERATOR_MAPPING = { + 'exact': '= %s', + 'iexact': "LIKE %s ESCAPE '\\'", + 'contains': "LIKE %s ESCAPE '\\'", + 'icontains': "LIKE %s ESCAPE '\\'", + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': "LIKE %s ESCAPE '\\'", + 'endswith': "LIKE %s ESCAPE '\\'", + 'istartswith': "LIKE %s ESCAPE '\\'", + 'iendswith': "LIKE %s ESCAPE '\\'", +} diff --git a/django/db/backends/sqlite3/client.py b/django/db/backends/sqlite3/client.py new file mode 100644 index 0000000000..097218341f --- /dev/null +++ b/django/db/backends/sqlite3/client.py @@ -0,0 +1,6 @@ +from django.conf import settings +import os + +def runshell(): + args = ['', settings.DATABASE_NAME] + os.execvp('sqlite3', args) diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py new file mode 100644 index 0000000000..e845179e64 --- /dev/null +++ b/django/db/backends/sqlite3/creation.py @@ -0,0 +1,29 @@ +# SQLite doesn't actually support most of these types, but it "does the right +# thing" given more verbose field definitions, so leave them as is so that +# schema inspection is more useful. +DATA_TYPES = { + 'AutoField': 'integer', + 'BooleanField': 'bool', + 'CharField': 'varchar(%(maxlength)s)', + 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', + 'DateField': 'date', + 'DateTimeField': 'datetime', + 'FileField': 'varchar(100)', + 'FilePathField': 'varchar(100)', + 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', + 'ImageField': 'varchar(100)', + 'IntegerField': 'integer', + 'IPAddressField': 'char(15)', + 'ManyToManyField': None, + 'NullBooleanField': 'bool', + 'OneToOneField': 'integer', + 'PhoneNumberField': 'varchar(20)', + 'PositiveIntegerField': 'integer unsigned', + 'PositiveSmallIntegerField': 'smallint unsigned', + 'SlugField': 'varchar(%(maxlength)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'text', + 'TimeField': 'time', + 'URLField': 'varchar(200)', + 'USStateField': 'varchar(2)', +} diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py new file mode 100644 index 0000000000..c5fa738249 --- /dev/null +++ b/django/db/backends/sqlite3/introspection.py @@ -0,0 +1,50 @@ +from django.db import transaction +from django.db.backends.sqlite3.base import quote_name + +def get_table_list(cursor): + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") + return [row[0] for row in cursor.fetchall()] + +def get_table_description(cursor, table_name): + cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name)) + return [(row[1], row[2], None, None) for row in cursor.fetchall()] + +def get_relations(cursor, table_name): + raise NotImplementedError + +def get_indexes(cursor, table_name): + raise NotImplementedError + +# Maps SQL types to Django Field types. Some of the SQL types have multiple +# entries here because SQLite allows for anything and doesn't normalize the +# field type; it uses whatever was given. +BASE_DATA_TYPES_REVERSE = { + 'bool': 'BooleanField', + 'boolean': 'BooleanField', + 'smallint': 'SmallIntegerField', + 'smallinteger': 'SmallIntegerField', + 'int': 'IntegerField', + 'integer': 'IntegerField', + 'text': 'TextField', + 'char': 'CharField', + 'date': 'DateField', + 'datetime': 'DateTimeField', + 'time': 'TimeField', +} + +# This light wrapper "fakes" a dictionary interface, because some SQLite data +# types include variables in them -- e.g. "varchar(30)" -- and can't be matched +# as a simple dictionary lookup. +class FlexibleFieldLookupDict: + def __getitem__(self, key): + key = key.lower() + try: + return BASE_DATA_TYPES_REVERSE[key] + except KeyError: + import re + m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key) + if m: + return ('CharField', {'maxlength': int(m.group(1))}) + raise KeyError + +DATA_TYPES_REVERSE = FlexibleFieldLookupDict() |
