https://github.com/excelano/spsql
SQL REPL for SharePoint Lists via Microsoft Graph
https://github.com/excelano/spsql
Last synced: about 1 month ago
JSON representation
SQL REPL for SharePoint Lists via Microsoft Graph
- Host: GitHub
- URL: https://github.com/excelano/spsql
- Owner: excelano
- License: mit
- Created: 2026-05-12T17:53:26.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-02T03:43:13.000Z (about 1 month ago)
- Last Synced: 2026-06-02T04:23:28.348Z (about 1 month ago)
- Language: Go
- Size: 136 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# spsql
A SQL REPL for SharePoint Lists. Bind to a single list at startup, then run SELECT, UPDATE, DELETE, and INSERT against it through the Microsoft Graph API.
## Example
```
$ spsql --list https://contoso.sharepoint.com/sites/team/Lists/Tasks
Authenticated as: alice@contoso.com
Connected to: Tasks (12 columns)
spsql REPL — type "help" for commands, "quit" to exit.
spsql> SELECT Title, Status WHERE Priority > 2
| Title | Status |
| ------------------ | ----------- |
| Migrate auth layer | Open |
| Backfill activity | In Progress |
(2 rows)
spsql> SELECT DISTINCT Status
| Status |
| ----------- |
| Open |
| In Progress |
| Done |
(3 rows)
spsql> SELECT Title, Modified ORDER BY Modified DESC LIMIT 3
| Title | Modified |
| ------------------ | -------------------- |
| Migrate auth layer | 2024-03-12T09:14:00Z |
| Backfill activity | 2024-03-09T17:02:00Z |
| Q3 invoice cleanup | 2024-02-28T11:48:00Z |
(3 rows)
spsql> UPDATE SET Status = 'Done' WHERE Modified < '2024-01-01'
Would update 8 rows in Tasks:
SET Status = "Done"
Sample:
| id | Title |
| -- | ------------------ |
| 41 | Q3 invoice cleanup |
| 47 | Audit log purge |
... 6 more
Apply? [y/N]: y
Updated 8 of 8 rows.
```
## Why
SharePoint list management at scale is painful. The web UI gives up past a handful of rows, OData `$filter` gives you query but not mutation, and Power Automate flows for bulk updates are slow to build and slower to run. spsql is the smallest tool that solves the bulk mutation problem: write one SQL statement, see what it would affect, commit if it is right.
## Install
On Debian or Ubuntu, install from the [Excelano apt repository](https://excelano.com/apt/) — add the repo once, then `apt upgrade` keeps it current:
```sh
curl -fsSL https://excelano.com/apt/setup.sh | sudo sh
sudo apt install spsql
```
From source (Go 1.24 or later):
```
go install github.com/excelano/spsql@latest
```
## Usage
### Interactive REPL
```
spsql --list
```
Opens a prompt bound to the list. Arrow keys recall history, Ctrl-R searches it, Ctrl-D exits. History persists at `~/.config/spsql/history` across sessions.
The REPL accepts SQL statements one per line plus a few meta-commands as plain words (case-insensitive): `help` or `?` shows command help, `describe` prints the column schema, `refresh` re-fetches the schema, and `quit` or `exit` leaves the REPL.
Writes (INSERT, UPDATE, DELETE) preview by default. spsql prints the affected count, a sample of the rows that match, and then prompts `Apply? [y/N]:`. Anything but `y` cancels. Append `!` to skip the prompt and commit immediately:
```sql
UPDATE SET Status = 'Done' WHERE Modified < '2024-01-01' !
```
### One-shot mode
```
spsql --list --exec ""
```
Runs one statement and exits. Writes need `--commit`; a bare DELETE (no WHERE clause) additionally needs `--confirm-destructive`. Output auto-detects to ASCII table on an interactive terminal and TSV when piped. Override with `--format=json` for JSON, useful for scripts that consume the results.
`SELECT *` returns visible user-defined columns by default. Pass `--all-fields` at startup to include hidden and system fields in `*` expansions; the flag works in both REPL and one-shot modes.
## Authentication
spsql signs in via OAuth device code flow against a multi-tenant public client Azure App Registration. By default it uses Excelano's published registration. On first run you will see a prompt to visit `https://microsoft.com/devicelogin` and enter a code, then sign in with the work account that has access to the list. The token is cached at `~/.config/spsql/token.json` and refreshed silently after that.
If you would rather use your own Azure App Registration (for audit log attribution or tenant policy), see [SELF-HOSTING.md](SELF-HOSTING.md). The override path is fork-and-build: clone, change one constant, `go build`. There is no runtime override flag, by design.
## SQL subset
spsql implements a deliberately small SQL grammar focused on what works against a single SharePoint list through Graph: SELECT and DML with literal values, simple WHERE predicates, no JOINs, no subqueries. See [GRAMMAR.md](GRAMMAR.md) for the full specification.
## Security
spsql is a CLI that runs locally and asks for delegated `Sites.ReadWrite.All`, scoped to what the signing user already has access to. Tokens stay on the machine; there is no telemetry. See [SECURITY.md](SECURITY.md) for the full policy and the vulnerability reporting process.
## License
MIT. See [LICENSE](LICENSE).