From e320a0936e4c846e7332bcacc533dda19dad6783 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Fri, 22 Jul 2005 03:15:43 +0000 Subject: [PATCH] Added sqlite3 database backend -- somewhat tested, but probably not 100% perfect. git-svn-id: http://code.djangoproject.com/svn/django/trunk@288 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/project_template/settings/main.py | 10 +- django/core/db/backends/sqlite3.py | 148 ++++++++++++++++++ docs/faq.txt | 5 +- docs/tutorial01.txt | 80 ++++++---- 4 files changed, 202 insertions(+), 41 deletions(-) create mode 100644 django/core/db/backends/sqlite3.py diff --git a/django/conf/project_template/settings/main.py b/django/conf/project_template/settings/main.py index a34bac53593..0a4eb8cc2e3 100644 --- a/django/conf/project_template/settings/main.py +++ b/django/conf/project_template/settings/main.py @@ -10,11 +10,11 @@ MANAGERS = ADMINS LANGUAGE_CODE = 'en-us' -DATABASE_ENGINE = 'postgresql' # 'postgresql' or 'mysql' -DATABASE_NAME = '' -DATABASE_USER = '' -DATABASE_PASSWORD = '' -DATABASE_HOST = '' # Set to empty string for localhost +DATABASE_ENGINE = 'postgresql' # 'postgresql', 'mysql', or 'sqlite' +DATABASE_NAME = '' # or path to database file if using sqlite +DATABASE_USER = '' # not used with sqlite +DATABASE_PASSWORD = '' # not used with sqlite +DATABASE_HOST = '' # Set to empty string for localhost; not used with sqlite SITE_ID = 1 diff --git a/django/core/db/backends/sqlite3.py b/django/core/db/backends/sqlite3.py new file mode 100644 index 00000000000..9599b79b7dc --- /dev/null +++ b/django/core/db/backends/sqlite3.py @@ -0,0 +1,148 @@ +""" +SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/). +""" + +from django.core.db import base, typecasts +from django.core.db.dicthelpers import * +from pysqlite2 import dbapi2 as Database +DatabaseError = Database.DatabaseError + +# Register adaptors ########################################################### + +Database.register_converter("bool", lambda s: str(s) == '1') +Database.register_converter("time", typecasts.typecast_time) +Database.register_converter("date", typecasts.typecast_date) +Database.register_converter("datetime", typecasts.typecast_timestamp) + +# Database wrapper ############################################################ + +class DatabaseWrapper: + def __init__(self): + self.connection = None + self.queries = [] + + def cursor(self): + from django.conf.settings import DATABASE_NAME, DEBUG + if self.connection is None: + self.connection = Database.connect(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) + if DEBUG: + return base.CursorDebugWrapper(FormatStylePlaceholderCursor(self.connection), self) + return FormatStylePlaceholderCursor(self.connection) + + 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 FormatStylePlaceholderCursor(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" (which I belive is true of other wrappers as well). + """ + + def execute(self, query, params=[]): + query = self.convert_query(query, len(params)) + return Database.Cursor.execute(self, query, params) + + def executemany(self, query, params=[]): + query = self.convert_query(query, len(params)) + return Database.Cursor.executemany(self, query, params) + + def convert_query(self, query, num_params): + # XXX this seems too simple to be correct... is this right? + return query % tuple("?" * num_params) + +# Helper functions ############################################################ + +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 = typecasts.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 _sqlite_date_trunc(lookup_type, dt): + try: + dt = typecasts.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) + +# Operators and fields ######################################################## + +OPERATOR_MAPPING = { + 'exact': '=', + 'iexact': 'LIKE', + 'contains': 'LIKE', + 'icontains': 'LIKE', + 'ne': '!=', + 'gt': '>', + 'gte': '>=', + 'lt': '<', + 'lte': '<=', + 'startswith': 'LIKE', + 'endswith': 'LIKE', + 'istartswith': 'LIKE', + 'iendswith': 'LIKE', +} + +# 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', + 'EmailField': 'varchar(75)', + 'FileField': '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(50)', + 'SmallIntegerField': 'smallint', + 'TextField': 'text', + 'TimeField': 'time', + 'URLField': 'varchar(200)', + 'USStateField': 'varchar(2)', + 'XMLField': 'text', +} diff --git a/docs/faq.txt b/docs/faq.txt index c82198acc83..097f22ed4f7 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -144,8 +144,8 @@ own lightweight development server. For a production environment, we recommend `Apache 2`_ and mod_python_, although Django follows the WSGI_ spec, which means it can run on a variety of server platforms. -You'll also need a database engine. PostgreSQL_ is recommended, and MySQL_ is -supported. +You'll also need a database engine. PostgreSQL_ is recommended, and MySQL_ +and `SQLite 3`_ are supported. .. _Python: http://www.python.org/ .. _Apache 2: http://httpd.apache.org/ @@ -153,6 +153,7 @@ supported. .. _WSGI: http://www.python.org/peps/pep-0333.html .. _PostgreSQL: http://www.postgresql.org/ .. _MySQL: http://www.mysql.com/ +.. _`SQLite 3`: http://www.sqlite.org/ Do I have to use mod_python? ---------------------------- diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt index 1f14cd4e803..eb3c0eee450 100644 --- a/docs/tutorial01.txt +++ b/docs/tutorial01.txt @@ -49,23 +49,27 @@ settings. Let's look at what ``startproject`` created:: First, edit ``myproject/settings/main.py``. It's a normal Python module with module-level variables representing Django settings. Edit the file and change these settings to match your database's connection parameters: + + * ``DATABASE_ENGINE`` -- Either 'postgresql', 'mysql' or 'sqlite3'. + More coming soon. + * ``DATABASE_NAME`` -- The name of your database, or the full path to + the database file if using sqlite. + * ``DATABASE_USER`` -- Your database username (not used for sqlite). + * ``DATABASE_PASSWORD`` -- Your database password (not used for sqlite). + * ``DATABASE_HOST`` -- The host your database is on. Leave this as an + empty string if your database server is on the same physical machine + (not used for sqlite). -* ``DATABASE_ENGINE`` -- Either 'postgresql' or 'mysql'. More coming soon. -* ``DATABASE_NAME`` -- The name of your database. -* ``DATABASE_USER`` -- Your database username. -* ``DATABASE_PASSWORD`` -- Your database password. -* ``DATABASE_HOST`` -- The host your database is on. Leave this as an - empty string if your database server is on the same physical machine - (localhost). +.. admonition:: Note -(Make sure you've created a database within PostgreSQL or MySQL by this point. -Do that with "``CREATE DATABASE database_name;``" within your database's -interactive prompt.) + Make sure you've created a database within PostgreSQL or MySQL by this + point. Do that with "``CREATE DATABASE database_name;``" within your + database's interactive prompt. -Also, note that MySQL support is a recent development, and Django hasn't been -comprehensively tested with that database. If you find any bugs in Django's -MySQL bindings, please file them in `Django's ticket system`_ so we can fix them -immediately. + Also, note that MySQL and sqlite support is a recent development, and Django + hasn't been comprehensively tested with either database. If you find any + bugs in those bindings, please file them in `Django's ticket system`_ so we + can fix them immediately. Now, take a second to make sure ``myproject`` is on your Python path. You can do this by copying ``myproject`` to Python's ``site-packages`` directory, @@ -90,8 +94,9 @@ On Windows, you'd use ``set`` instead:: If you don't see any errors after running ``django-admin.py init``, you know it worked. That command initialized your database with Django's core database -tables. If you're interested, run the PostgreSQL or MySQL command-line client -and type "\\dt" (PostgreSQL) or "SHOW TABLES;" (MySQL) to display the tables. +tables. If you're interested, run the command-line client for your database and +type ``\\dt`` (PostgreSQL), ``SHOW TABLES;`` (MySQL), or ``.schema`` (SQLite) to +display the tables. Now you're set to start doing work. You won't have to take care of this boring administrative stuff again. @@ -235,27 +240,34 @@ You should see the following (the CREATE TABLE SQL statements for the polls app) Note the following: -* Table names are automatically generated by combining the name of the app - (polls) with a plural version of the object name (polls and choices). (You - can override this behavior.) -* Primary keys (IDs) are added automatically. (You can override this, too.) -* The foreign key relationship is made explicit by a ``REFERENCES`` statement. -* It's tailored to the database you're using, so database-specific field types - such as ``auto_increment`` (MySQL) vs. ``serial`` (PostgreSQL) are handled - for you automatically. The author of this tutorial runs PostgreSQL, so the - example output is in PostgreSQL syntax. + * Table names are automatically generated by combining the name of the app + (polls) with a plural version of the object name (polls and choices). (You + can override this behavior.) + + * Primary keys (IDs) are added automatically. (You can override this, too.) + + * The foreign key relationship is made explicit by a ``REFERENCES`` statement. + + * It's tailored to the database you're using, so database-specific field types + such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or ``integer + primary key`` (SQLite) are handled for you automatically. The author of + this tutorial runs PostgreSQL, so the example output is in PostgreSQL + syntax. If you're interested, also run the following commands: -* ``django-admin.py sqlinitialdata polls`` -- Outputs the initial-data inserts - required for Django's admin framework. -* ``django-admin.py sqlclear polls`` -- Outputs the necessary ``DROP TABLE`` - statements for this app, according to which tables already exist in your - database (if any). -* ``django-admin.py sqlindexes polls`` -- Outputs the ``CREATE INDEX`` - statements for this app. -* ``django-admin.py sqlall polls`` -- A combination of 'sql' and - 'sqlinitialdata'. + * ``django-admin.py sqlinitialdata polls`` -- Outputs the initial-data + inserts required for Django's admin framework. + + * ``django-admin.py sqlclear polls`` -- Outputs the necessary ``DROP + TABLE`` statements for this app, according to which tables already exist + in your database (if any). + + * ``django-admin.py sqlindexes polls`` -- Outputs the ``CREATE INDEX`` + statements for this app. + + * ``django-admin.py sqlall polls`` -- A combination of 'sql' and + 'sqlinitialdata'. Looking at the output of those commands can help you understand what's actually happening under the hood.