What SQLite CAN actually do
Tags: sqlite, sql, discuss, database
Published: 2020-05-03 07:17:08

Not sure about other RDBMS's.

## JSON handling

This is possible via [JSON1 extension](https://www.sqlite.org/json1.html). I believe the functions and syntaxes are different in PostGRES.

```sql
SELECT json_extract(col1, '$.c[2]') FROM table
WHERE json_extract(col2, '$.x') > 3;
```

## RegExp handling

[The extension must be installed first](https://www.sqlite.org/lang_expr.html#regexp), unlike JSON1.

I am not sure how this works for Python and Node.js, though. [Maybe, this in better-sqlite3](https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/api.md#loadextensionpath-entrypoint---this), but [better-sqlite3 has a problem with Electron](https://github.com/JoshuaWise/better-sqlite3/issues/126). This is important, because LIKE in SQLite is case-insensitive (i.e. ILIKE).

```sql
SELECT field FROM table WHERE field REGEXP '123';
```

## User-defined functions

I don't sure how this works either. I'd be happy to write and compile C myself.

## Sort Null first, Null last

This is a built-in functionality, just like aggregate functions.

```sql
SORT BY field ASC NULLS LAST
```

## Add column and rename table

This is easy, as in [ALTER TABLE](https://www.sqlite.org/lang_altertable.html)

```sql
ALTER TABLE table_name ADD COLUMN column_definition;
```

## Overcoming ALTER TABLE / COLUMN [limitations](https://www.sqlitetutorial.net/sqlite-alter-table/)

This is done by

1. Create new table
2. Copy data
3. Drop old table
4. Rename new into old

So, to create a new table, you must know to get schema.

It's called `PRAGMA`. It generates a table. If you want to preview this in sqlite CLI, don't forget to `.header on`.

```sql
PRAGMA table_info(table_name);
```

```sql
PRAGMA index_list(table_name);
```

```sql
PRAGMA index_info(index_name);
```

So, it becomes,

```sql
-- disable foreign key constraint check
PRAGMA foreign_keys=off;

-- start a transaction
BEGIN TRANSACTION;

-- Here you can drop column or rename column
CREATE TABLE IF NOT EXISTS new_table( 
   column_definition,
   ...
);
-- copy data from the table to the new_table
INSERT INTO new_table(column_list)
SELECT column_list
FROM table;

-- drop the table
DROP TABLE table;

-- rename the new_table to the table
ALTER TABLE new_table RENAME TO table; 

-- commit the transaction
COMMIT;

-- enable foreign key constraint check
PRAGMA foreign_keys=on;
```

This is [my implementation](https://github.com/patarapolw/hotcell/blob/master/packages/server/src/api/table.ts#L338).

## Bonus

### Listing table names programmatically

There is a hidden table called `sqlite_master`.

```sql
SELECT [name] FROM sqlite_master
WHERE [type] = 'table' AND [name] NOT LIKE 'sqlite_%';
```

### Safe identifier names for tables and columns

I made a function for this, which I used in [Liteorm](https://github.com/patarapolw/liteorm) and [this new project](https://github.com/patarapolw/hotcell).

```ts
/**
 * https://www.sqlite.org/lang_keywords.html
 * @param s identifier
 */
export function safeColumnName (s: string) {
  const keywords = `
    ABORT
    ACTION
    ADD
    AFTER
    ALL
    ALTER
    ALWAYS
    ANALYZE
    AND
    AS
    ASC
    ATTACH
    AUTOINCREMENT
    BEFORE
    BEGIN
    BETWEEN
    BY
    CASCADE
    CASE
    CAST
    CHECK
    COLLATE
    COLUMN
    COMMIT
    CONFLICT
    CONSTRAINT
    CREATE
    CROSS
    CURRENT
    CURRENT_DATE
    CURRENT_TIME
    CURRENT_TIMESTAMP
    DATABASE
    DEFAULT
    DEFERRABLE
    DEFERRED
    DELETE
    DESC
    DETACH
    DISTINCT
    DO
    DROP
    EACH
    ELSE
    END
    ESCAPE
    EXCEPT
    EXCLUDE
    EXCLUSIVE
    EXISTS
    EXPLAIN
    FAIL
    FILTER
    FIRST
    FOLLOWING
    FOR
    FOREIGN
    FROM
    FULL
    GENERATED
    GLOB
    GROUP
    GROUPS
    HAVING
    IF
    IGNORE
    IMMEDIATE
    IN
    INDEX
    INDEXED
    INITIALLY
    INNER
    INSERT
    INSTEAD
    INTERSECT
    INTO
    IS
    ISNULL
    JOIN
    KEY
    LAST
    LEFT
    LIKE
    LIMIT
    MATCH
    NATURAL
    NO
    NOT
    NOTHING
    NOTNULL
    NULL
    NULLS
    OF
    OFFSET
    ON
    OR
    ORDER
    OTHERS
    OUTER
    OVER
    PARTITION
    PLAN
    PRAGMA
    PRECEDING
    PRIMARY
    QUERY
    RAISE
    RANGE
    RECURSIVE
    REFERENCES
    REGEXP
    REINDEX
    RELEASE
    RENAME
    REPLACE
    RESTRICT
    RIGHT
    ROLLBACK
    ROW
    ROWS
    SAVEPOINT
    SELECT
    SET
    TABLE
    TEMP
    TEMPORARY
    THEN
    TIES
    TO
    TRANSACTION
    TRIGGER
    UNBOUNDED
    UNION
    UNIQUE
    UPDATE
    USING
    VACUUM
    VALUES
    VIEW
    VIRTUAL
    WHEN
    WHERE
    WINDOW
    WITH
    WITHOUT`
    .split('\n')
    .map((el) => el.trim())
    .filter((el) => el)

  /**
   * https://stackoverflow.com/questions/31788990/sqlite-what-are-the-restricted-characters-for-identifiers
   */
  const validIdToken = 'A-Z0-9_$:'
  const kwRegex = new RegExp(`(^|[^${validIdToken}\\)])(${keywords.join('|')})($|[^${validIdToken}\\()])`, 'gi')

  return s.replace(kwRegex, (_, p1, p2, p3) => {
    return `${p1}"${p2.replace(/"/g, '["]')}"${p3}`
  })
}
```

So, I actually made a SQLite editor for nothing. Not sure about practicality. Originally made for NoSQL editor, but no offline NoSQL beats SQLite in terms of safety and functionality.

It is made to be somewhat similar to AirTable and Google Sheets; but is local and is optimized for a database.

{% github patarapolw/hotcell no-readme %}