Repository navigation
Expand file tree
/
Copy pathProgram.fs
More file actions
80 lines (71 loc) · 3.18 KB
/
Copy pathProgram.fs
File metadata and controls
80 lines (71 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// fss_sqlite: a tiny command-line to-do list stored in a SQLite file.
//
// dotnet run --project samples/SqliteTodo -- add "buy milk"
// dotnet run --project samples/SqliteTodo -- list
// dotnet run --project samples/SqliteTodo -- done 1
// dotnet run --project samples/SqliteTodo (no args: runs a scripted demo)
//
// Set TODO_DB to choose the database file (default: todo.db in the temp folder).
open System
open System.IO
open Fss.Data.SQLite
// SQLite has few native types: integers come back as int64, booleans are stored as 0/1
// and dates as text. fss converts to the record's field types, so `bool` and `int` are fine.
type Todo = { id : int64 ; title : string ; completed : bool ; created : string ; due : string option }
/// Shape used for inserts: no id (SQLite assigns it)
type NewTodo = { title : string ; completed : bool ; created : string ; due : string option }
let schema = """
create table if not exists todo (
id integer primary key,
title text not null,
completed integer not null default 0,
created text not null,
due text
)"""
let openDb () =
let path =
match Environment.GetEnvironmentVariable "TODO_DB" with
| null | "" -> Path.Combine(Path.GetTempPath(), "fss-todo.db")
| p -> p
let db = new DynamicSqlConnection(sprintf "Data Source=%s" path)
db.ExecuteNonQuery schema |> ignore
db
let add (db:DynamicSqlConnection) title due =
// SQLite returns the new rowid (int64) via last_insert_rowid()
let id : int64 =
db.InsertOne({ title = title ; completed = false ; created = DateTime.UtcNow.ToString "s" ; due = due }, table = "todo")
printfn "added #%d %s" id title
id
let list (db:DynamicSqlConnection) =
let todos = db.Query<Todo> "select * from todo order by completed, id" |> List.ofSeq
if todos.IsEmpty then printfn "(nothing to do)"
for t in todos do
let due = t.due |> Option.map (sprintf " (due %s)") |> Option.defaultValue ""
printfn " [%s] #%d %s%s" (if t.completed then "x" else " ") t.id t.title due
todos
let complete (db:DynamicSqlConnection) (id:int64) =
// SQLite parameters can be written @name, :name or $name
match db.ExecuteNonQuery("update todo set completed = 1 where id = @id", [ "id", box id ]) with
| 0 -> printfn "no to-do #%d" id
| _ -> printfn "completed #%d" id
[<EntryPoint>]
let main argv =
use db = openDb ()
match argv |> List.ofArray with
| [ "add" ; title ] -> add db title None |> ignore ; 0
| [ "add" ; title ; due ] -> add db title (Some due) |> ignore ; 0
| [ "list" ] -> list db |> ignore ; 0
| [ "done" ; id ] -> complete db (int64 id) ; 0
| [] ->
// Scripted demo in a throwaway database
db.ExecuteNonQuery "delete from todo" |> ignore
let a = add db "write the docs" (Some "2026-11-01")
add db "publish to NuGet" None |> ignore
add db "celebrate" None |> ignore
complete db a
let todos = list db
if todos.Length <> 3 || not (todos |> List.exists (fun t -> t.id = a && t.completed)) then failwith "unexpected state"
0
| _ ->
eprintfn "usage: SqliteTodo [add <title> [due] | list | done <id>]"
1