summaryrefslogtreecommitdiff
path: root/src/generics.lisp
blob: 99b3dc92dcc504eb4b7d3e07309036bd940c312c (plain)
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
(declaim (optimize (speed 0) (safety 3) (debug 3)))

(defpackage :org-ckons-sql
  (:use :cl)
  (:export :id
           :*project
           :*table
           :*where-expression
           :record
           :postgres-record
           :record-pkg
           :get-record
           :get-records
           :insert-record
           :update-record
           :update-record-no-nulls
           :delete-record
           :delete-records
           :call-pg-function
           :intersect-slots
           :slot-is-field-p
           :simple-date-to-date
           :sanitize-json
           :with-database))

(in-package :org-ckons-sql)

(defgeneric to-date (record date-string)
  (:documentation "In PostgreSQL, a string in the format `YYYY-MM-DD
HH:MM:SS' can be used directly to populate a date field. Therefore,
this function does nothing except to put quotes arount the value."))

(defgeneric sanitize (record field)
  (:documentation "If `field' is a lisp `nil', returns `NULL'.  Also
converts datestrings to a PLSQL to_date() function call, converts
numbers to oracle-friendly representations, and puts single ticks
around string values.  All munging, workarounds, hacks, etc. to make
the interface between lisp and PostgreSQL work better should be done
here."))

(defgeneric call-pg-function* (record)
  (:documentation "Calls a Postgresql function that returns something
other than a result set that matches up with the passed-in `record'
which is always a postgresql-record, or returns nothing at all."))

(defgeneric field-names-string (record)
  (:documentation "Builds a string of SQL query field names."))

(defgeneric field-values-string (record)
  (:documentation "Builds a string of SQL query values to match the
field names returned by `field-names-string'."))

(defgeneric field-value-pairs (record)
  (:documentation "Builds a string of SQL query field/value pairs."))

(defgeneric field-value-pairs-no-nulls (record)
  (:documentation "Builds a string of SQL query field/value pairs, but
only those fields with a non-null value are included."))

(defgeneric field-value-list (record)
  (:documentation "Builds a list of SQL query field/value pairs, but
only those fields with a non-null value are included."))

(defgeneric field-value-list-no-nulls (record)
  (:documentation "Builds a list of SQL query field/value pairs, but
only those fields with a non-null value are included."))

(defgeneric field-plist-no-nulls (record)
  (:documentation "Returns the non-null field slots of `record' as a
plist. Used for record serialization."))

(defgeneric build-where-clause (record)
  (:documentation "Builds a WHERE clause from the non-nil slots in
`record'."))

(defgeneric intersect-slots (record slots)
  (:documentation "Since the built-in `intersect' function does not
take package name prefixes into account, and since `map-slot-names'
returns slot names prefixed with the package name, this method was
written to intersect lists ignoring package prefixes."))

(defgeneric expand-where-expression (record where-expression)
  (:documentation "All WHERE clauses must pass through this method.
If the WHERE clause contains all hard-coded values, then it will make
it through untouched.  If it contains ~a placeholders, it will be
parsed and expanded using the values contained in `record'.  Finally
the WHERE keyword is prepended."))

(defgeneric insert-query (record)
  (:documentation "Builds an INSERT query to save the state of
`record' in the database. Does not include `nil' slots."))

(defgeneric currval-query (record)
  (:documentation "Calls currval() to get the last inserted ID. This
must be called with the same database connection that performed the
prior INSERT, and `record' must have a `*table' that can be used as
the first arguemnt to pg_get_serial_sequence()."))

(defgeneric update-query (record)
  (:documentation "Builds an UPDATE query to save the state of
`record' in the database."))

(defgeneric select-query (record)
  (:documentation "Builds a SELECT query to retrieve the state of
`record' from the database."))

(defgeneric delete-query (record)
  (:documentation "Builds a DELETE query to to delete a `record' from
the database.  CAUTION: use this only on `record' objects whose
backend tables are real tables, not inlined table functions."))

(defgeneric insert-record* (record)
  (:documentation "Inserts the state of `record' into the database,
and fetches the newly-created ID into the `id' slot of `record'."))

(defgeneric update-record* (record)
  (:documentation "Saves the state of `record' to the database."))

(defgeneric select-records-impl* (record where-clause order-by-clause)
  (:documentation "Low-level function which is to be called via
`select-records'.  Executes a SELECT statement with the given WHERE
clause and ORDER BY clause. Do not include the WHERE and ORDER BY
keywords in your arguments, as they will be automatically
prepended."))

(defgeneric select-records* (record order-by-clause)
  (:documentation "High-level interface to `select-records-impl'.
Executes a SELECT statement with the given ORDER BY clause, with a
WHERE clause automatically built by AND-separating the name/values
stored in `record', and fetches the result set of the query into a
list of `record' objects.  Do not include the ORDER BY keyword in
`order-by-clause', as it will be automatically prepended."))

(defgeneric select-records-where* (record where-expression order-by-clause)
  (:documentation "High-level interface to `select-records-impl'.
Executes a SELECT statement with the given WHERE clause and ORDER BY
clause.  `where-expression' is a FORMAT-style string which uses ~a for
value placeholders.  The slots of `record' that correspond to the
field names in `where-expression' are used to fill in the
placeholders.  Fetches the result set of the query into a list of
`record' objects.  Do not include the WHERE and ORDER BY keywords in
`where-expression' and `order-by-clause', as they will be
automatically prepended."))

(defgeneric delete-records-impl* (record where-expression)
  (:documentation "Low-level function which is to be called via
`delete-records'.  Executes a DELETE statement with the given WHERE
clause.  Do not include the WHERE keyword in your argument, as this
method automatically prepends it."))

(defgeneric delete-record* (record)
  (:documentation "High-level interface to `delete-records-impl'.
Convenience function for deleting records by `id'. record' must
conform to a certain standard pattern. Executes a DELETE statement
with a WHERE clause built with `*where-expression' and `id'. Assumes
that `*where-expression' is the format string id = ~a."))

(defgeneric delete-records* (record)
  (:documentation "High-level interface to `delete-records-impl'.
Executes a DELETE statement with a WHERE clause automatically built by
AND-separating the name/values stored in `record'."))

(defgeneric delete-records-where* (record where-expression)
  (:documentation "High-level interface to `delete-records-impl'.
Executes a DELETE statement with the given WHERE clause.
`where-expression' is a FORMAT-style string which uses ~a for value
placeholders.  The slots of `record' that correspond to the field
names in `where-expression' are used to fill in the placeholders.Do
not include the WHERE keyword in `where-expression', as it will be
automatically prepended."))

(defgeneric load-record (record row)
  (:documentation "Populates the slots of the `record' subclass from
the fields in `row', which are usually obtained by running a SELECT
query."))

(defgeneric sanitize-json (record-or-service)
  (:documentation "Untaints the record or service object so that it's
safe to serialize into JSON."))

(defgeneric insert-record (record-pkg record)
  (:documentation "Inserts a new record into the table associated with
`record', using the data from `record'."))

(defgeneric update-record (record-pkg record)
  (:documentation "Updates an existing record in the table associated
with `record', using the data from `record'."))

(defgeneric delete-record (record-pkg record)
  (:documentation "Deletes an existing record in the table associated
with `record', using `id' and `*where-expression' from `record'."))

(defgeneric delete-records (record-pkg record)
  (:documentation "Deletes an existing record in the table associated
with `record', using the data from `record'."))

(defgeneric delete-records-where (record-pkg record where-expression)
  (:documentation "Updates an existing record in the table associated
with `record', using the passed-in `where-expression'."))

(defgeneric get-records (record-pkg record order-by-clause)
  (:documentation "All-purpose method for issuing a SELECT statement
to obtain a result set of `record' objects.  The instance of `record'
passed into this method is used to hold input parameters, including
the table name.  Any slots that are populated with a non-nil value
will be used in the AND-separated WHERE clause.  Fetches the result
set of the query into a list of `record' objects."))

(defgeneric get-records-where (record-pkg record where-expression order-by-clause)
  (:documentation "All-purpose method for issuing a SELECT statement
to obtain a result set of `record' objects.  The instance of `record'
passed into this method is used to hold input parameters, including
the table name.  `where-expression' is a FORMAT-style string which
uses ~a for value placeholders.  The slots of `record' that correspond
to the field names in `where-expression' are used to fill in the
placeholders.  Fetches the result set of the query into a list of
`record' objects."))

(defgeneric get-record (record-pkg record)
  (:documentation "Returns a single `record' object.  The instance of
`record' passed into this method is used to hold input parameters,
including the table name.  Any slots that are populated with a non-nil
value will be used in the AND-separated WHERE clause.  Meant for
queries that return only one unique row.  Returns `nil' if no rows
were found matching the query.  Warning: if more than one row is
returned by the query, this method will return the first one.  Note
that you cannot control which row will be first because of the lack of
an ORDER BY clause.  Use this only for queries that are supposed to
return no more than one row."))

(defgeneric get-record-where (record-pkg record where-expression)
  (:documentation "Returns a single `record' object.  The instance of
`record' passed into this method is used to hold input parameters,
including the table name.  `where-expression' is a FORMAT-style string
which uses ~a for value placeholders.  The slots of `record' that
correspond to the field names in `where-expression' are used to fill
in the placeholders.  Meant for queries that return only one unique
row.  Returns `nil' if no rows were found matching the query.
Warning: if more than one row is returned by the query, this method
will return the first one.  Note that you cannot control which row
will be first because of the lack of an ORDER BY clause.  Use this
only for queries that are supposed to return no more than one row."))

(defgeneric call-pg-function (record-pkg postgresql-record)
  (:documentation ""))