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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
use super::QueryContext;
use crate::{
    error::Error,
    model::{Model, Mutation, Query},
    Map,
};
use std::borrow::Cow;

/// Hooks for the model.
pub trait ModelHooks: Model {
    /// Model data.
    type Data: Default = ();
    /// Extension data.
    type Extension: Clone + Send + Sync + 'static = ();

    /// A hook running before extracting the model data.
    #[inline]
    async fn before_extract() -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after extracting the model data.
    #[inline]
    async fn after_extract(&mut self, _extension: Self::Extension) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running before validating the model data.
    #[inline]
    async fn before_validation(
        _model: &mut Map,
        _extension: Option<&Self::Extension>,
    ) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after validating the model data.
    #[inline]
    async fn after_validation(&mut self, _model: &mut Map) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running before creating the table.
    #[inline]
    async fn before_create_table() -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after creating the table.
    #[inline]
    async fn after_create_table() -> Result<(), Error> {
        Ok(())
    }

    /// A hook running before scanning the table.
    #[inline]
    async fn before_scan(query: &str) -> Result<QueryContext, Error> {
        let ctx = QueryContext::new();
        let query_id = ctx.query_id().to_string();
        tracing::debug!(query_id, query);
        Ok(ctx)
    }

    /// A hook running after scanning the table.
    async fn after_scan(ctx: &QueryContext) -> Result<(), Error> {
        let query_id = ctx.query_id().to_string();
        let query = ctx.query();
        let arguments = ctx.format_arguments();
        let message = match ctx.rows_affected() {
            Some(0) => Cow::Borrowed("no rows affected or fetched"),
            Some(1) => Cow::Borrowed("only one row affected or fetched"),
            Some(num_rows) if num_rows > 1 => {
                Cow::Owned(format!("{num_rows} rows affected or fetched"))
            }
            _ => Cow::Borrowed("the query result has not been recorded"),
        };
        let execution_time = ctx.start_time().elapsed();
        let execution_time_millis = execution_time.as_millis();
        if execution_time_millis > 3000 {
            tracing::warn!(
                query_id,
                query,
                arguments,
                execution_time_millis,
                "{message}"
            );
        } else if execution_time_millis > 1000 {
            tracing::info!(
                query_id,
                query,
                arguments,
                execution_time_millis,
                "{message}"
            );
        } else {
            tracing::debug!(
                query_id,
                query,
                arguments,
                execution_time_micros = execution_time.as_micros(),
                "{message}"
            );
        }
        Ok(())
    }

    /// A hook running before inserting a model into the table.
    #[inline]
    async fn before_insert(&mut self) -> Result<Self::Data, Error> {
        Ok(Self::Data::default())
    }

    /// A hook running after inserting a model into the table.
    #[inline]
    async fn after_insert(ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to insert a model into the table");
        }
        ctx.emit_metrics("insert");
        Ok(())
    }

    /// A hook running before deleting a model from the table.
    #[inline]
    async fn before_delete(&mut self) -> Result<Self::Data, Error> {
        Ok(Self::Data::default())
    }

    /// A hook running after deleting a model from the table.
    #[inline]
    async fn after_delete(self, ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
        let query = ctx.query();
        let query_id = ctx.query_id().to_string();
        if ctx.is_success() {
            tracing::warn!(query, query_id, "a model was deleted from the table");
        } else {
            tracing::error!(query, query_id, "fail to detele a model from the table");
        }
        ctx.emit_metrics("delete");
        Ok(())
    }

    /// A hook running before logically deleting a model from the table.
    #[inline]
    async fn before_soft_delete(&mut self) -> Result<Self::Data, Error> {
        Ok(Self::Data::default())
    }

    /// A hook running after logically deleting a model from the table.
    #[inline]
    async fn after_soft_delete(ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to logically delete a model from the table");
        }
        ctx.emit_metrics("soft_delete");
        Ok(())
    }

    /// A hook running before locking a model in the table.
    #[inline]
    async fn before_lock(&mut self) -> Result<Self::Data, Error> {
        Ok(Self::Data::default())
    }

    /// A hook running after locking a model in the table.
    #[inline]
    async fn after_lock(ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to lock a model in the table");
        }
        ctx.emit_metrics("lock");
        Ok(())
    }

    /// A hook running before updating a model in the table.
    #[inline]
    async fn before_update(&mut self) -> Result<Self::Data, Error> {
        Ok(Self::Data::default())
    }

    /// A hook running after updating a model in the table.
    #[inline]
    async fn after_update(ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to update a model in the table");
        }
        ctx.emit_metrics("update");
        Ok(())
    }

    /// A hook running before updating or inserting a model into the table.
    #[inline]
    async fn before_upsert(&mut self) -> Result<Self::Data, Error> {
        Ok(Self::Data::default())
    }

    /// A hook running after updating or inserting a model into the table.
    #[inline]
    async fn after_upsert(ctx: &QueryContext, _data: Self::Data) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to upsert a model into the table");
        }
        ctx.emit_metrics("upsert");
        Ok(())
    }

    /// A hook running before counting the models in the table.
    #[inline]
    async fn before_count(_query: &Query) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after counting the models in the table.
    #[inline]
    async fn after_count(ctx: &QueryContext) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to count the models in the table");
        }
        ctx.emit_metrics("count");
        Ok(())
    }

    /// A hook running before selecting the models with a `Query` from the table.
    #[inline]
    async fn before_query(_query: &Query) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after selecting the models with a `Query` from the table.
    #[inline]
    async fn after_query(ctx: &QueryContext) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to select the models from the table");
        }
        ctx.emit_metrics("query");
        Ok(())
    }

    /// A hook running before updating the models with a `Mutation` in the table.
    #[inline]
    async fn before_mutation(_query: &Query, _mutation: &mut Mutation) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after updating the models with a `Mutation` in the table.
    #[inline]
    async fn after_mutation(ctx: &QueryContext) -> Result<(), Error> {
        if !ctx.is_success() {
            ctx.record_error("fail to update the models in the table");
        }
        ctx.emit_metrics("mutation");
        Ok(())
    }

    /// A hook running before listing the models with a `Query` from the table.
    #[inline]
    async fn before_list(
        _query: &mut Query,
        _extension: Option<&Self::Extension>,
    ) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running after decoding the model as a `Map`.
    #[inline]
    async fn after_decode(_model: &mut Map) -> Result<(), Error> {
        Ok(())
    }

    /// A hook running before returning the model data as a HTTP response.
    #[inline]
    async fn before_respond(
        _model: &mut Map,
        _extension: Option<&Self::Extension>,
    ) -> Result<(), Error> {
        Ok(())
    }
}