SQLite

SQLite (https://www.sqlite.org/) is a library for implementing a self-contained embedded SQL Database, and bills itself as the most [widely used] database in the world.

In this module, we will discuss how SQLite differs from things like PostgreSQL and MySQL, and the basics of getting started and creating a database

Embedded Database

What is an embedded database? An embedded database means that the database is tightly integrated with the application using the database, as opposed to the database being a separate application, or an application stored on a separate server.

Systems like MySQL and PostgreSQL run a database as a server. Even when the database server is physically on the same hardware as the application, interactions with MySQL and PostgreSQL are done in a client-server nature. SQLite, on the other hand, is invoked directly, with no need for a separate server application running locally.

Specifically, in SQLite, the “database” is stored as literal files on the disk. These files are typically stored with the extension “.sqlite3” or “.db” - example “sde_database.sqlite3”, (you’ll also see “.sqlite” as well). Be aware, however, that we don’t want to store a database file as

Usages of SQLite

SQLite is great for local storage of data, and is frequently used to store settings and local data for desktop and mobile applications. For instance, many web browsers use SQLite for storing history, bookmarks, and settings. SQLite is included by default in Android, iOS, Mac OS X 10.4 and onwards, and Windows 10 and onwards. Many web application frameworks support using SQLite for storage.

SQLite database files are portable across platforms and operating systems, making data easy to share.

Limitations of SQLite

In general, SQLite is ill-suited for high-traffic client-server situations. Usually, when building a website that you expect a large degree of concurrent traffic, MySQL and PostgreSQL are going to be preferred for their scalability. Specifically, SQLite is worse when there are a large degrees of data writes (that is, adding new data or changing existing data).

SQLite can function as a database for a website on the server side, but in general if I were hosting a web application, I’d prefer to use PostgreSQL.

SQLite is also ill-suited for very large datasets. While SQLite can theoretically store ~281 Terabytes, the entire database is a single file on disk, and most file systems have significantly smaller limits.

Additionally, if your software requires a high degree of concurrency, with several processes needing to write at the same time, SQLite can become a bottleneck. While SQLite allows for an unlimited number of reading processes, for data synchronicity it only allows one writer at a time, and so writers have to queue, even when writing to different unrelated areas of the same database.

sqlite3 command line

A full guide of the command line can be found here, but I will highlight some keys.

SQLite Installation

First, you need to install sqlite3 and add the sqlite3 command to your system PATH variable - a full guide for Windows and Mac OS X can be found on TutorialsPoint here

Be aware when using SQLite command line that the command is sqlite3, NOT sqlite! You can ensure sqlite3 is installed and added to the Path correctly using from your command line program of choice:

> sqlite3 --version

Note that for this module, I will include the > symbol to indicate the start of a terminal command. You would not type the “>”. So above, you would simply type “sqlite3 –version”

Creating and Opening a database

To create or open an existing SQLite database, we simply use sqlite3 [database_filename.sqlite3]. For example:

> sqlite3 data.sqlite3

Would either:

  • Open an existing database file in your current folder named “data.sqlite3”
  • If no such database file exists, then create a new database file named “data.sqlite3” in your current folder.

A that point, you will enter the SQLite Shell, which is it’s own command line program. You will see something like:

SQLite version 3.39.0 2022-06-25 14:57:57
sqlite>

If we use sqlite3 data.sqlite3 (or whatever filename our database is), that database will automatically be opened when the sqlite shell begins. If we just use the sqlite3 with no filename, the sqlite shell will open with an in-memory database (more on that later)

sqlite shell commands

Before we get into creating and storing tables, let’s review some basic commands. Many shell commands start with a period “.” to distinguish these system commands from sql queries such as “create”, “select”, “insert”, etc.

.help

sqlite> .help

This will list all commands available in .sqlite shell. You can follow .help with a specific command to get additional information about that command. For example:

sqlite> .help open
.open ?OPTIONS? ?FILE?   Close existing database and reopen FILE
     Options:
        --append        Use appendvfs to append database to the end of FILE
        --deserialize   Load into memory using sqlite3_deserialize()
        --hexdb         Load the output of "dbtotxt" as an in-memory db
        --maxsize N     Maximum size for --hexdb or --deserialized database
        --new           Initialize FILE to an empty database
        --nofollow      Do not follow symbolic links
        --readonly      Open FILE readonly
        --zip           FILE is a ZIP archive

.open

In the SQLite shell, .open is used to open a different database file, closing the currently open file if one exists. For instance, if we currently have a memory-database open, but we want to switch to an existing database named “data.sqlite3” in our current directory, we can use:

sqlite> .open data.sqlite3

.quit

.quit is used to quit the SQLite shell

sqlite> .quit

This will exit the sqlite shell and bring your terminal back to its default state. For instance, on my Windows computer, this would look like:

sqlite> .quit
PS C:\Users\pm8fc\fall24\sqlite_example>

And now I’m back in Powershell, the terminal I used to open sqlite3

.tables

This command is used to list all Tables in the current database (if you are following along, you won’t have any tables yet, as we will create those in the next module).

.dump

Creates a sql script that can be run to recreate the contents of the array in its current state. By default, this script will print to the console. Note that this does not modify the database nor delete any tables.

This is often used with the .output command to dump the contents to a file, and the .read command to run the dumped out .sql file as a script.

.output

By default, sqlite displays writes all contents to the console. For instance:

sqlite> .output myFile.txt
sqlite> .help
slqite> .quit
> cat myFile.txt

This will result in myFile.txt containing the output of the .help command. Be aware that all commands after .output will be output to the same file. To stop writing to a file and write to the console again, use:

sqlite> .output dump.sql
sqlite> .dump
sqlite> .output

The above commands, in order, mean:
1) .output dump.sql - send the display of all future commands to the file dump.sql (Be aware this overwrites any existing file - it does not append!)
2) .dump - generate a sql script that will recreate all data in the database (written to dump.sql)
3) .output - write all future content to the console again (no more writing to file)

SQLite quirks

SQLite has a number of quirks where it behaves different than other database systems, or differently than you might expect. SQLite has a full article here, but I will highlight some important ones.

Note that many of these quirks reference content covered in upcoming modules, so you may not know what they mean yet. That is okay.

No User Access Control

First, many database systems provide robust “user access” control. For instance, some database user accounts may be admin accounts with access to the complete database, including read and write access on all data and all tables. Other accounts may only be able to insert data into certain tables, or only query information from other tables. Additionally, when connecting to the database, users will “log in” with a username and password. (Note that by database user account, we mean an account specifically for the database, not for an account for an application that uses the database)

SQLite does not support this. SQLite has no access control permissions, nor any concept of a “database user”. Additionally, by default, sqlite files are not encrypted, although there is an extension to add encryption. You should be aware of this when storing any secure information.

Lack of datatype enforcement

SQLite is “flexibly typed”. This means that, by direct query usage, you can insert Text into an attribute that is an Integer or Real. Additionally, while most database systems allow you to fix a maximum length for datatypes (for instance, VARCHAR(20) would be a String of max length 20), SQLite does not enforce this limitation. This is an intended feature of SQLite.

SQLite does allow users to enable enforcement of datatypes in tables by using STRICT tables.

Nullable Primary Key

So, in general, a primary key should never be null. However, due to a bug in the early days of SQLite, null primary keys were allowed, provided there was only one null primary key (as the uniqueness constraint was still enforced). Because many legacy systems that use SQLite may have one or more tables with a null primary key, this bug was never fixed. However, this can be worked around in a couple ways.

  • Use INTEGER PRIMARY KEY in your tables. You should generally do this anyways, since primary keys should almost always be integers. If a table has an INTEGER PRIMARY KEY, attempting to insert null will result in a unique integer automatically being stored in the primary key attribute
  • Add NOT NULLABLE constraint to Primary Key when not using Integers

Foreign Keys are off by default

SQLite initially doesn’t support foreign key constraint enforcement. While support was added later, like Nullable Primary Keys, there was a fear that adding enforcement would break existing databases. As such, foreign key enforcement must be turned on with the following SQLite query:

PRAGMA foreign_keys = ON

In general, you will be expected to do this on all assignments where foreign key constraints are enforced.

Text Literals != Integer Literals

1 = '1' (that is, the integer 1 equals the Text “1”) is FALSE. In nearly all other SQL systems, this would be TRUE.

Lack of Boolean and DateTime

SQLite doesn’t have a “true” Boolean Datatype. Instead, if an attribute’s data type is stated as Boolean, it’s actually an integer (1 for TRUE, 0 for FALSE). TRUE and FALSE are reserved words which represent 1 and 0 respectively.

Additionally, there is no DateTime datatype. Instead, SQLite uses built-in DateTime functions for converting Text, Integer, or Real to a DateTime and vice versa (the most common usage is using an Integer with Unix Time, which represents the number of seconds since January 1, 1970, midnight UTC)


Previous submodule:
Next submodule: