[Racket] Ferti hydroponic nutrient solver, redux.
1
#lang racket
2
3
(provide migrate-all!)
4
5
(require db
6
sql
7
"conn.rkt")
8
9
(define migrations-box (box '()))
10
11
(define (migrate-all!)
12
(for ([pair (in-list (unbox migrations-box))])
13
(match-define (cons _ stmts) pair)
14
(with-tx (for ([stmt (in-list stmts)])
15
(query-exec (current-conn) stmt)))))
16
17
(define-syntax-rule (define-migration migration-name sql)
18
(let ([migrations (unbox migrations-box)]
19
[name-symbol (string->symbol migration-name)])
20
(if (assoc name-symbol migrations)
21
(error 'define-migration "migration '~a' declared more than once" migration-name)
22
(set-box! migrations-box (append migrations (list (cons name-symbol sql)))))))
23
24
;;;;;;;;
25
;; USERS
26
;;;;;;;;
27
28
(define-migration "create table users"
29
(list (create-table #:if-not-exists users
30
#:columns [id integer #:not-null]
31
[name text #:not-null]
32
[role_id integer]
33
#:constraints (primary-key id)
34
(unique name)
35
(foreign-key role_id #:references (user_roles id)))))
36
37
(define-migration "create table user_roles"
38
(list (create-table #:if-not-exists user_roles
39
#:columns [id integer #:not-null]
40
[name text #:not-null]
41
#:constraints (primary-key id)
42
(unique name))))
43
44
;;;;;;;;;;;;
45
;; NUTRIENTS
46
;;;;;;;;;;;;
47
48
(define-migration "create table nutrients"
49
(list (create-table #:if-not-exists nutrients
50
#:columns [id integer #:not-null]
51
[canonical_name text #:not-null]
52
[french_name text #:not-null]
53
[formula text #:not-null]
54
#:constraints (primary-key id)
55
(unique canonical_name)
56
(unique formula))))
57
58
(define-migration
59
"create table nutrient_value_sets and indexes"
60
(list
61
(create-table
62
#:if-not-exists nutrient_value_sets
63
#:columns [id integer #:not-null]
64
[nutrient_measurement_id integer]
65
[crop_requirement_id integer]
66
[fertilizer_product_id integer]
67
#:constraints (primary-key id)
68
(foreign-key nutrient_measurement_id #:references (nutrient_measurements id) #:on-delete #:cascade)
69
(foreign-key crop_requirement_id #:references (crop_requirements id) #:on-delete #:cascade)
70
(foreign-key fertilizer_product_id #:references (fertilizer_products id) #:on-delete #:cascade)
71
(unique nutrient_measurement_id)
72
(unique crop_requirement_id)
73
(unique fertilizer_product_id)
74
(check (or (and (is-not-null nutrient_measurement_id)
75
(is-null crop_requirement_id)
76
(is-null fertilizer_product_id))
77
(and (is-null nutrient_measurement_id)
78
(is-null crop_requirement_id)
79
(is-not-null fertilizer_product_id))
80
(and (is-null nutrient_measurement_id)
81
(is-not-null crop_requirement_id)
82
(is-null fertilizer_product_id)))))
83
"CREATE INDEX IF NOT EXISTS idx_nvs_meas ON nutrient_value_sets(nutrient_measurement_id)"
84
"CREATE INDEX IF NOT EXISTS idx_nvs_crop ON nutrient_value_sets(crop_requirement_id)"
85
"CREATE INDEX IF NOT EXISTS idx_nvs_prod ON nutrient_value_sets(fertilizer_product_id)"))
86
87
(define-migration
88
"create table nutrient_values and indexes"
89
(list
90
(create-table #:if-not-exists nutrient_values
91
#:columns [value_set_id integer #:not-null]
92
[nutrient_id integer #:not-null]
93
[value_ppm real #:not-null]
94
#:constraints (primary-key value_set_id nutrient_id)
95
(foreign-key value_set_id #:references (nutrient_value_sets id) #:on-delete #:cascade)
96
(foreign-key nutrient_id #:references (nutrients id) #:on-delete #:cascade))
97
"CREATE INDEX IF NOT EXISTS idx_nv_set_nutrient ON nutrient_values(value_set_id, nutrient_id)"))
98
99
(define-migration "create table nutrient_measurements"
100
(list (create-table #:if-not-exists nutrient_measurements
101
#:columns [id integer #:not-null]
102
;; ISO8601 date
103
[measurement_date text #:not-null]
104
#:constraints (primary-key id)
105
(unique measurement_date))))
106
107
;;;;;;;;
108
;; CROPS
109
;;;;;;;;
110
111
(define-migration "create table crops"
112
(list (create-table #:if-not-exists crops
113
#:columns [id integer #:not-null]
114
[canonical_name text #:not-null]
115
#:constraints (primary-key id)
116
(unique canonical_name))))
117
118
(define-migration
119
"create table crop_requirements"
120
(list (create-table #:if-not-exists crop_requirements
121
#:columns [id integer #:not-null]
122
[crop_id integer]
123
[profile text #:not-null]
124
#:constraints (primary-key id)
125
(foreign-key crop_id #:references (crops id) #:on-delete #:cascade))))
126
127
(define-migration "create table crop_rotations"
128
(list (create-table #:if-not-exists crop_rotations
129
#:columns [id integer #:not-null]
130
;; ISO8601 date
131
[rotation_date text #:not-null]
132
#:constraints (primary-key id)
133
(unique rotation_date))))
134
135
(define-migration
136
"create table crop_rotation_requirements"
137
(list (create-table
138
#:if-not-exists crop_rotation_requirements
139
#:columns [crop_rotation_id integer #:not-null]
140
[crop_requirement_id integer #:not-null]
141
[proportion_percent integer #:not-null]
142
#:constraints (primary-key crop_rotation_id crop_requirement_id)
143
(foreign-key crop_rotation_id #:references (crop_rotations id) #:on-delete #:cascade)
144
(foreign-key crop_requirement_id #:references (crop_requirements id) #:on-delete #:cascade))))
145
146
;;;;;;;;;;;;;;
147
;; FERTILIZERS
148
;;;;;;;;;;;;;;
149
150
(define-migration "create table fertilizer_products"
151
(list (create-table #:if-not-exists fertilizer_products
152
#:columns [id integer #:not-null]
153
[canonical_name text #:not-null]
154
[brand_name text #:not-null]
155
#:constraints (primary-key id)
156
(unique canonical_name brand_name))))
157
158
(module+ test
159
(connect!)
160
(migrate-all!))
161