Working with SQLite

INTRO

Most, if not all, Android devices have an sqlite program in there somewhere,  Android uses sqlite databases as a part of its infrastructure. The good news is the program is available to us in AppInventor, and we can use it to manage large datasets and relational databases in our apps.

There have been several paid and free extensions written by developers for use in AppInventor. One of these, "aix-SQLite" written by Tom Bennedum, is open sourced. I have forked this code, and stripped it back to make a barebones or expert sqlite extension, far fewer blocks, but retaining all the functionality. I have named this SimpleSqlite and will use this extension to demonstrate how to use the sqlite program and databases.

SimpleSqlite Extension features:


See here for the best tutorial resource on SQLite functions

https://www.sqlitetutorial.net

You will need to interpret some of the guidance there in order to apply it, or disregard it!

HOW DOES IT WORK ?


DATABASE FILE MANAGEMENT OPERATIONS

The Default Database

Let us find out where our default database is located, and what its name is:

See that I have to use OPEN DATABASE in order to run the SELECT command

It may be useful to save this path to a variable/tinydb, so that you can return to the default database SimpleSQLite.db without having to restart the app.

(Note you can also use the DbName property block, or the getDatabasePath method block to return the database name)

Set a Different Database as Default

I have another database called "peopleCodes.db" I will set this as default on initialisation:

Notice that for above and below, the index/id for both is "0", and the "name" is "main" (default)

Start with the Default Database, and IMPORT another database from the assets

In this instance, I have a database file in the assets called empty.db

See that I have to CLOSE DATABASE in order to carry out the IMPORT function

The extension returns "true" for a successful import

Any data that was in the default database is gone! I can now work with the data imported, but this is now in the default database....

IMPORT a Database from Assets, then EXPORT it to a File

I have peopleCodes.db in the assets, this is a quick way of moving it to my ASD, so that it is writable.

I must remember that if I work on the default database now, with the peopleCodes data, I will need to EXPORT it again
in order to update the peopleCodes.db. Alternatively, set the DBName to peopleCodes.db and OPEN DATABASE before working on the data

ATTACH a Database to the Default Database

Here I will ATTACH peopleCodes.db in the ASD to SimpleSqlite.db 

Label 1 now shows the "main" database and "peopleCodes" database

Notice how I had to use single quotes around the file path

Non-SELECT functions return "completed"

The Attachment does not survive an app restart

To DETACH a Database

EXPORT a Database

This exports the current database to a database file in the ASD with the set name

EXPORT a Database to Shared Directories

This exports the current database to a database file in the /Download directory with the set name. (You can also use the /Documents directory in the same way)

Set the DBName Property to a Database in Shared Directories

This sets the sqlite database to the db file in the /Download directory. You must use an absolute path as shown. You can then work directly with this database in that location. Note that a journal file is created in the same directory when you work with the database

DATABASE SQL


Some useful commands you can run

This will show you the program version of sqlite on your device

e.g. 3.22

SELECT sqlite_version()


This will show you the names of the tables in the current database

e.g. ["data", "android_metadata"]
You can ignore the android_metadata table, this is for system use only

SELECT name FROM sqlite_master WHERE type='table'


This will show you the structure or schema of the tables in your database

e.g. [[0, id, INTEGER, 1, null, 1], [1, full_name, TEXT, 0, null, 0], [2, code, TEXT, 0, null, 0]]
As you can see there are three columns...each column entry displays: 

You use the above settings when creating a table and its columns

SELECT * FROM pragma_table_info('table_name')


Just return the column names in a table

e.g. [id, full_name, code]

SELECT name FROM pragma_table_info('table_name')


CRUD

Create a Table in a Database

Let us say we want a table to record some simple contact info. We will need an id, a name, and a location. Make sure you OPEN the database. We will run the following all as one command:

CREATE TABLE IF NOT EXISTS "contacts" ("id" INTEGER NOT NULL, "name" TEXT, "location" TEXT, PRIMARY KEY("id"))

You can usually get away with not using all the double quotes


Insert a record to our new table

INSERT INTO contacts(name,location) VALUES('Bob','London')

Notice I did not have to provide an entry for the id, this is automatically handled for me, because of the settings I provided.

Also notice the need for single quotes around each entry in VALUES


Update a record in our table

UPDATE contacts SET location = 'Glasgow' WHERE id = 1

This will replace London with Glasgow in the record where the id = 1 (Bob's record)


Delete a record in our table

DELETE FROM contacts WHERE id = 1

The record with id = 1 will be removed


Query Data in our database

SELECT * FROM data WHERE name LIKE 'B%' LIMIT 10

As an example, this query will return up to 10 records from the "data" table, where the name starts with "B"

Once you become familiar with the query language, you will be doing it just for fun ;)


In practice I have found a quirk with the return of data from a database using SELECT. This also exists in the original extension by Tom Bennedum.

If you run a SELECT query and only seek to return values from a single column in a table, all is well, and a working AI2 list is returned.

e.g. SELECT animals FROM data ... 

["dog", "cat", "hen"]

However, if you run a SELECT query  and seek to return two or more columns (or all columns[*], or a more complex SELECT] then the extension appears to return a valid list of lists. This is true for the outer list, but the inner lists are returned as some kind of JSON/List object that AI2 list blocks do not understand.

e.g. SELECT * FROM data ... 

[[dog,cat,hen],[lion,tiger,puma],[sheep,horse,cow]]

Use the ListFixer block to return a working AI2 list of lists. 

This will then return data in the correct format:

e.g. [["dog","cat,"hen"],["lion","tiger","puma"],["sheep","horse","cow"]]

COLUMN HEADERS

With regard to column headers, the output is pretty ugly, because it returns the headers for each item in the SELECT output (rather than as another single list item). You need to set the column Headers property before running the SQL:

and without

Hence it is probably better to just fetch the column names....

and then insert this list to any SELECT output list, e.g.

MILLIS


Another quirk with SimpleSqlite that has popped up is how sqlite seems to handle millis when returning the data to AI2. If you use INTEGER as the column field, then sqlite thinks you are using a date/time value, and converts it to a different (sometimes negative) value. The way to overcome this is to store millis as TEXT, then all is well. AI2 is quite happy with handling numbers as strings.