Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/database/formats.rs

25.7 KiB, 48 runs

created by r1870400018:8428, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! SQL and NoSQL projections of the datetime types.
2//!
3//! Schema generation, INSERT values and query fragments for relational stores,
4//! and document projections for MongoDB or CouchDB style stores.
5//!
6//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
7//! Anthropic Claude
8
9use crate::{
10 time::{CalClock, CalClockZone},
11 clock::ClockTime,
12 calendar::CalendarDate,
13 database::StorageFormat,
14};
15
16use oxedyne_fe2o3_core::prelude::*;
17use oxedyne_fe2o3_jdat::prelude::*;
18
19use std::collections::HashMap;
20
21pub trait SqlCompatible {
22 fn sql_data_type(format: StorageFormat) -> &'static str;
23
24 fn sql_create_table(table_name: &str, format: StorageFormat) -> String;
25
26 fn sql_create_indexes(table_name: &str) -> Vec<String>;
27
28 fn to_sql_values(&self, format: StorageFormat) -> Outcome<HashMap<String, String>>;
29
30 /// Each pair is a name and a WHERE clause using positional $1, $2 placeholders.
31 fn sql_query_fragments() -> Vec<(&'static str, &'static str)>;
32}
33
34impl SqlCompatible for CalClock {
35 fn sql_data_type(format: StorageFormat) -> &'static str {
36 match format {
37 StorageFormat::Binary => "BIGINT, VARCHAR(50)", // nanos, timezone
38 StorageFormat::Iso8601 => "VARCHAR(35)", // ISO 8601 string
39 StorageFormat::Component => "JSONB", // JSON object
40 StorageFormat::UnixTimestamp => "BIGINT, VARCHAR(50)", // millis, timezone
41 }
42 }
43
44 fn sql_create_table(table_name: &str, format: StorageFormat) -> String {
45 match format {
46 StorageFormat::Binary => fmt!(
47 "CREATE TABLE {} (\n\
48 id SERIAL PRIMARY KEY,\n\
49 timestamp_nanos BIGINT NOT NULL,\n\
50 timezone VARCHAR(50) NOT NULL,\n\
51 year INTEGER NOT NULL,\n\
52 month SMALLINT NOT NULL,\n\
53 day SMALLINT NOT NULL,\n\
54 hour SMALLINT NOT NULL,\n\
55 minute SMALLINT NOT NULL,\n\
56 second SMALLINT NOT NULL,\n\
57 nanosecond INTEGER NOT NULL,\n\
58 day_of_week SMALLINT NOT NULL,\n\
59 day_of_year INTEGER NOT NULL,\n\
60 is_leap_second BOOLEAN NOT NULL DEFAULT FALSE,\n\
61 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
62 );", table_name
63 ),
64
65 StorageFormat::Iso8601 => fmt!(
66 "CREATE TABLE {} (\n\
67 id SERIAL PRIMARY KEY,\n\
68 iso_timestamp VARCHAR(35) NOT NULL,\n\
69 timezone VARCHAR(50) NOT NULL,\n\
70 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
71 );", table_name
72 ),
73
74 StorageFormat::Component => fmt!(
75 "CREATE TABLE {} (\n\
76 id SERIAL PRIMARY KEY,\n\
77 datetime_components JSONB NOT NULL,\n\
78 year INTEGER GENERATED ALWAYS AS ((datetime_components->>'year')::INTEGER) STORED,\n\
79 month INTEGER GENERATED ALWAYS AS ((datetime_components->>'month')::INTEGER) STORED,\n\
80 day INTEGER GENERATED ALWAYS AS ((datetime_components->>'day')::INTEGER) STORED,\n\
81 timezone VARCHAR(50) GENERATED ALWAYS AS (datetime_components->>'timezone') STORED,\n\
82 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
83 );", table_name
84 ),
85
86 StorageFormat::UnixTimestamp => fmt!(
87 "CREATE TABLE {} (\n\
88 id SERIAL PRIMARY KEY,\n\
89 unix_millis BIGINT NOT NULL,\n\
90 timezone VARCHAR(50) NOT NULL,\n\
91 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
92 );", table_name
93 ),
94 }
95 }
96
97 fn sql_create_indexes(table_name: &str) -> Vec<String> {
98 vec![
99 fmt!("CREATE INDEX idx_{}_timestamp ON {} (timestamp_nanos);", table_name, table_name),
100 fmt!("CREATE INDEX idx_{}_timezone ON {} (timezone);", table_name, table_name),
101 fmt!("CREATE INDEX idx_{}_date ON {} (year, month, day);", table_name, table_name),
102 fmt!("CREATE INDEX idx_{}_time ON {} (hour, minute);", table_name, table_name),
103 fmt!("CREATE INDEX idx_{}_year_month ON {} (year, month);", table_name, table_name),
104 fmt!("CREATE INDEX idx_{}_day_of_week ON {} (day_of_week);", table_name, table_name),
105 fmt!("CREATE INDEX idx_{}_leap_second ON {} (is_leap_second) WHERE is_leap_second = TRUE;", table_name, table_name),
106 ]
107 }
108
109 fn to_sql_values(&self, format: StorageFormat) -> Outcome<HashMap<String, String>> {
110 let mut values = HashMap::new();
111
112 match format {
113 StorageFormat::Binary => {
114 let nanos = res!(self.to_nanos_since_epoch());
115 values.insert("timestamp_nanos".to_string(), nanos.to_string());
116 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
117 values.insert("year".to_string(), self.year().to_string());
118 values.insert("month".to_string(), self.month().to_string());
119 values.insert("day".to_string(), self.day().to_string());
120 values.insert("hour".to_string(), self.hour().to_string());
121 values.insert("minute".to_string(), self.minute().to_string());
122 values.insert("second".to_string(), self.second().to_string());
123 values.insert("nanosecond".to_string(), self.nanosecond().to_string());
124 values.insert("day_of_week".to_string(), (self.day_of_week().of() as u8).to_string());
125 values.insert("day_of_year".to_string(), res!(self.day_of_year()).to_string());
126 values.insert("is_leap_second".to_string(), self.is_leap_second().to_string());
127 },
128
129 StorageFormat::Iso8601 => {
130 values.insert("iso_timestamp".to_string(), fmt!("'{}'", res!(self.to_iso8601())));
131 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
132 },
133
134 StorageFormat::Component => {
135 let components = fmt!(
136 "'{{\
137 \"year\": {}, \
138 \"month\": {}, \
139 \"day\": {}, \
140 \"hour\": {}, \
141 \"minute\": {}, \
142 \"second\": {}, \
143 \"nanosecond\": {}, \
144 \"timezone\": \"{}\"\
145 }}'",
146 self.year(), self.month(), self.day(),
147 self.hour(), self.minute(), self.second(), self.nanosecond(),
148 self.zone().id()
149 );
150 values.insert("datetime_components".to_string(), components);
151 },
152
153 StorageFormat::UnixTimestamp => {
154 let millis = res!(self.to_millis());
155 values.insert("unix_millis".to_string(), millis.to_string());
156 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
157 },
158 }
159
160 Ok(values)
161 }
162
163 fn sql_query_fragments() -> Vec<(&'static str, &'static str)> {
164 vec![
165 ("date_range", "timestamp_nanos BETWEEN $1 AND $2"),
166 ("year_filter", "year = $1"),
167 ("month_filter", "year = $1 AND month = $2"),
168 ("day_filter", "year = $1 AND month = $2 AND day = $3"),
169 ("timezone_filter", "timezone = $1"),
170 ("time_range", "hour BETWEEN $1 AND $2"),
171 ("weekend_filter", "day_of_week IN (6, 7)"), // Saturday, Sunday
172 ("weekday_filter", "day_of_week BETWEEN 1 AND 5"),
173 ("leap_seconds", "is_leap_second = TRUE"),
174 ("recent_dates", "timestamp_nanos >= $1"), // Recent since timestamp
175 ]
176 }
177}
178
179impl SqlCompatible for ClockTime {
180 fn sql_data_type(format: StorageFormat) -> &'static str {
181 match format {
182 StorageFormat::Binary => "BIGINT, VARCHAR(50)", // nanos_of_day, timezone
183 StorageFormat::Iso8601 => "TIME", // ISO time
184 StorageFormat::Component => "JSONB", // JSON object
185 StorageFormat::UnixTimestamp => "INTEGER, VARCHAR(50)", // millis_of_day, timezone
186 }
187 }
188
189 fn sql_create_table(table_name: &str, format: StorageFormat) -> String {
190 match format {
191 StorageFormat::Binary => fmt!(
192 "CREATE TABLE {} (\n\
193 id SERIAL PRIMARY KEY,\n\
194 nanos_of_day BIGINT NOT NULL,\n\
195 timezone VARCHAR(50) NOT NULL,\n\
196 hour SMALLINT NOT NULL,\n\
197 minute SMALLINT NOT NULL,\n\
198 second SMALLINT NOT NULL,\n\
199 nanosecond INTEGER NOT NULL,\n\
200 is_leap_second BOOLEAN NOT NULL DEFAULT FALSE,\n\
201 is_end_of_day BOOLEAN NOT NULL DEFAULT FALSE,\n\
202 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
203 );", table_name
204 ),
205
206 StorageFormat::Iso8601 => fmt!(
207 "CREATE TABLE {} (\n\
208 id SERIAL PRIMARY KEY,\n\
209 time_value TIME NOT NULL,\n\
210 timezone VARCHAR(50) NOT NULL,\n\
211 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
212 );", table_name
213 ),
214
215 StorageFormat::Component => fmt!(
216 "CREATE TABLE {} (\n\
217 id SERIAL PRIMARY KEY,\n\
218 time_components JSONB NOT NULL,\n\
219 hour INTEGER GENERATED ALWAYS AS ((time_components->>'hour')::INTEGER) STORED,\n\
220 minute INTEGER GENERATED ALWAYS AS ((time_components->>'minute')::INTEGER) STORED,\n\
221 timezone VARCHAR(50) GENERATED ALWAYS AS (time_components->>'timezone') STORED,\n\
222 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
223 );", table_name
224 ),
225
226 StorageFormat::UnixTimestamp => fmt!(
227 "CREATE TABLE {} (\n\
228 id SERIAL PRIMARY KEY,\n\
229 millis_of_day INTEGER NOT NULL,\n\
230 timezone VARCHAR(50) NOT NULL,\n\
231 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
232 );", table_name
233 ),
234 }
235 }
236
237 fn sql_create_indexes(table_name: &str) -> Vec<String> {
238 vec![
239 fmt!("CREATE INDEX idx_{}_nanos_of_day ON {} (nanos_of_day);", table_name, table_name),
240 fmt!("CREATE INDEX idx_{}_timezone ON {} (timezone);", table_name, table_name),
241 fmt!("CREATE INDEX idx_{}_hour ON {} (hour);", table_name, table_name),
242 fmt!("CREATE INDEX idx_{}_hour_minute ON {} (hour, minute);", table_name, table_name),
243 fmt!("CREATE INDEX idx_{}_leap_second ON {} (is_leap_second) WHERE is_leap_second = TRUE;", table_name, table_name),
244 ]
245 }
246
247 fn to_sql_values(&self, format: StorageFormat) -> Outcome<HashMap<String, String>> {
248 let mut values = HashMap::new();
249
250 match format {
251 StorageFormat::Binary => {
252 values.insert("nanos_of_day".to_string(), self.to_nanos_of_day().to_string());
253 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
254 values.insert("hour".to_string(), self.hour().of().to_string());
255 values.insert("minute".to_string(), self.minute().of().to_string());
256 values.insert("second".to_string(), self.second().of().to_string());
257 values.insert("nanosecond".to_string(), self.nanosecond().of().to_string());
258 values.insert("is_leap_second".to_string(), self.is_leap_second().to_string());
259 values.insert("is_end_of_day".to_string(), self.is_end_of_day().to_string());
260 },
261
262 StorageFormat::Iso8601 => {
263 values.insert("time_value".to_string(), fmt!("'{}'", self.to_iso_string()));
264 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
265 },
266
267 StorageFormat::Component => {
268 let components = fmt!(
269 "'{{\
270 \"hour\": {}, \
271 \"minute\": {}, \
272 \"second\": {}, \
273 \"nanosecond\": {}, \
274 \"timezone\": \"{}\"\
275 }}'",
276 self.hour().of(), self.minute().of(), self.second().of(),
277 self.nanosecond().of(), self.zone().id()
278 );
279 values.insert("time_components".to_string(), components);
280 },
281
282 StorageFormat::UnixTimestamp => {
283 values.insert("millis_of_day".to_string(), self.millis_of_day().to_string());
284 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
285 },
286 }
287
288 Ok(values)
289 }
290
291 fn sql_query_fragments() -> Vec<(&'static str, &'static str)> {
292 vec![
293 ("time_range", "nanos_of_day BETWEEN $1 AND $2"),
294 ("hour_filter", "hour = $1"),
295 ("hour_range", "hour BETWEEN $1 AND $2"),
296 ("minute_filter", "hour = $1 AND minute = $2"),
297 ("timezone_filter", "timezone = $1"),
298 ("morning_hours", "hour BETWEEN 6 AND 11"),
299 ("afternoon_hours", "hour BETWEEN 12 AND 17"),
300 ("evening_hours", "hour BETWEEN 18 AND 23"),
301 ("night_hours", "hour BETWEEN 0 AND 5"),
302 ("business_hours", "hour BETWEEN 9 AND 17"),
303 ("leap_seconds", "is_leap_second = TRUE"),
304 ("end_of_day", "is_end_of_day = TRUE"),
305 ]
306 }
307}
308
309impl SqlCompatible for CalendarDate {
310 fn sql_data_type(format: StorageFormat) -> &'static str {
311 match format {
312 StorageFormat::Binary => "BIGINT, VARCHAR(50)", // julian_day, timezone
313 StorageFormat::Iso8601 => "DATE", // ISO date
314 StorageFormat::Component => "JSONB", // JSON object
315 StorageFormat::UnixTimestamp => "INTEGER, VARCHAR(50)", // days_since_epoch, timezone
316 }
317 }
318
319 fn sql_create_table(table_name: &str, format: StorageFormat) -> String {
320 match format {
321 StorageFormat::Binary => fmt!(
322 "CREATE TABLE {} (\n\
323 id SERIAL PRIMARY KEY,\n\
324 julian_day BIGINT NOT NULL,\n\
325 timezone VARCHAR(50) NOT NULL,\n\
326 year INTEGER NOT NULL,\n\
327 month SMALLINT NOT NULL,\n\
328 day SMALLINT NOT NULL,\n\
329 day_of_week SMALLINT NOT NULL,\n\
330 day_of_year INTEGER NOT NULL,\n\
331 week_of_year SMALLINT NOT NULL,\n\
332 quarter SMALLINT NOT NULL,\n\
333 is_leap_year BOOLEAN NOT NULL DEFAULT FALSE,\n\
334 is_weekend BOOLEAN NOT NULL DEFAULT FALSE,\n\
335 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
336 );", table_name
337 ),
338
339 StorageFormat::Iso8601 => fmt!(
340 "CREATE TABLE {} (\n\
341 id SERIAL PRIMARY KEY,\n\
342 date_value DATE NOT NULL,\n\
343 timezone VARCHAR(50) NOT NULL,\n\
344 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
345 );", table_name
346 ),
347
348 StorageFormat::Component => fmt!(
349 "CREATE TABLE {} (\n\
350 id SERIAL PRIMARY KEY,\n\
351 date_components JSONB NOT NULL,\n\
352 year INTEGER GENERATED ALWAYS AS ((date_components->>'year')::INTEGER) STORED,\n\
353 month INTEGER GENERATED ALWAYS AS ((date_components->>'month')::INTEGER) STORED,\n\
354 day INTEGER GENERATED ALWAYS AS ((date_components->>'day')::INTEGER) STORED,\n\
355 timezone VARCHAR(50) GENERATED ALWAYS AS (date_components->>'timezone') STORED,\n\
356 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
357 );", table_name
358 ),
359
360 StorageFormat::UnixTimestamp => fmt!(
361 "CREATE TABLE {} (\n\
362 id SERIAL PRIMARY KEY,\n\
363 days_since_epoch INTEGER NOT NULL,\n\
364 timezone VARCHAR(50) NOT NULL,\n\
365 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n\
366 );", table_name
367 ),
368 }
369 }
370
371 fn sql_create_indexes(table_name: &str) -> Vec<String> {
372 vec![
373 fmt!("CREATE INDEX idx_{}_julian_day ON {} (julian_day);", table_name, table_name),
374 fmt!("CREATE INDEX idx_{}_timezone ON {} (timezone);", table_name, table_name),
375 fmt!("CREATE INDEX idx_{}_year ON {} (year);", table_name, table_name),
376 fmt!("CREATE INDEX idx_{}_year_month ON {} (year, month);", table_name, table_name),
377 fmt!("CREATE INDEX idx_{}_date ON {} (year, month, day);", table_name, table_name),
378 fmt!("CREATE INDEX idx_{}_day_of_week ON {} (day_of_week);", table_name, table_name),
379 fmt!("CREATE INDEX idx_{}_quarter ON {} (quarter);", table_name, table_name),
380 fmt!("CREATE INDEX idx_{}_weekend ON {} (is_weekend) WHERE is_weekend = TRUE;", table_name, table_name),
381 ]
382 }
383
384 fn to_sql_values(&self, format: StorageFormat) -> Outcome<HashMap<String, String>> {
385 let mut values = HashMap::new();
386
387 match format {
388 StorageFormat::Binary => {
389 values.insert("julian_day".to_string(), self.to_julian_day_number().to_string());
390 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
391 values.insert("year".to_string(), self.year().to_string());
392 values.insert("month".to_string(), self.month().to_string());
393 values.insert("day".to_string(), self.day().to_string());
394 values.insert("day_of_week".to_string(), (self.day_of_week().of() as u8).to_string());
395 values.insert("day_of_year".to_string(), res!(self.day_of_year()).to_string());
396 values.insert("week_of_year".to_string(), res!(self.week_of_year()).to_string());
397 values.insert("quarter".to_string(), ((self.month() - 1) / 3 + 1).to_string());
398 values.insert("is_leap_year".to_string(), self.is_leap_year().to_string());
399 values.insert("is_weekend".to_string(), self.is_weekend().to_string());
400 },
401
402 StorageFormat::Iso8601 => {
403 values.insert("date_value".to_string(), fmt!("'{}'", self.to_string()));
404 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
405 },
406
407 StorageFormat::Component => {
408 let components = fmt!(
409 "'{{\
410 \"year\": {}, \
411 \"month\": {}, \
412 \"day\": {}, \
413 \"timezone\": \"{}\"\
414 }}'",
415 self.year(), self.month(), self.day(), self.zone().id()
416 );
417 values.insert("date_components".to_string(), components);
418 },
419
420 StorageFormat::UnixTimestamp => {
421 if let Ok(days) = self.days_since_epoch() {
422 values.insert("days_since_epoch".to_string(), days.to_string());
423 }
424 values.insert("timezone".to_string(), fmt!("'{}'", self.zone().id()));
425 },
426 }
427
428 Ok(values)
429 }
430
431 fn sql_query_fragments() -> Vec<(&'static str, &'static str)> {
432 vec![
433 ("date_range", "julian_day BETWEEN $1 AND $2"),
434 ("year_filter", "year = $1"),
435 ("month_filter", "year = $1 AND month = $2"),
436 ("day_filter", "year = $1 AND month = $2 AND day = $3"),
437 ("timezone_filter", "timezone = $1"),
438 ("weekends", "is_weekend = TRUE"),
439 ("weekdays", "is_weekend = FALSE"),
440 ("day_of_week", "day_of_week = $1"),
441 ("quarter", "quarter = $1"),
442 ("leap_years", "is_leap_year = TRUE"),
443 ("recent_days", "julian_day >= $1"),
444 ("current_month", "year = EXTRACT(YEAR FROM NOW()) AND month = EXTRACT(MONTH FROM NOW())"),
445 ("current_year", "year = EXTRACT(YEAR FROM NOW())"),
446 ]
447 }
448}
449
450pub trait NoSqlDocument {
451 fn to_document(&self) -> HashMap<String, Dat>;
452
453 fn from_document(doc: &HashMap<String, Dat>) -> Outcome<Self>
454 where
455 Self: Sized;
456
457 fn index_fields() -> Vec<&'static str>;
458
459 fn compound_indexes() -> Vec<Vec<&'static str>>;
460}
461
462impl NoSqlDocument for CalClock {
463 fn to_document(&self) -> HashMap<String, Dat> {
464 let mut doc = HashMap::new();
465
466 // Primary timestamp data
467 if let Ok(nanos) = self.to_nanos_since_epoch() {
468 doc.insert("timestamp_nanos".to_string(), Dat::I64(nanos));
469 }
470 if let Ok(millis) = self.to_millis() {
471 doc.insert("timestamp_millis".to_string(), Dat::I64(millis));
472 }
473
474 // Date components
475 doc.insert("year".to_string(), Dat::I32(self.year()));
476 doc.insert("month".to_string(), Dat::U8(self.month()));
477 doc.insert("day".to_string(), Dat::U8(self.day()));
478
479 // Time components
480 doc.insert("hour".to_string(), Dat::U8(self.hour()));
481 doc.insert("minute".to_string(), Dat::U8(self.minute()));
482 doc.insert("second".to_string(), Dat::U8(self.second()));
483 doc.insert("nanosecond".to_string(), Dat::U32(self.nanosecond()));
484
485 // Calendar information
486 doc.insert("day_of_week".to_string(), Dat::U8(self.day_of_week().of() as u8));
487 if let Ok(day_of_year) = self.day_of_year() {
488 doc.insert("day_of_year".to_string(), Dat::U16(day_of_year));
489 }
490 if let Ok(week) = self.week_of_year() {
491 doc.insert("week_of_year".to_string(), Dat::U8(week));
492 }
493 doc.insert("quarter".to_string(), Dat::U8((self.month() - 1) / 3 + 1));
494
495 // Timezone
496 doc.insert("timezone".to_string(), Dat::Str(self.zone().id().to_string()));
497
498 // Computed fields for queries
499 doc.insert("date_str".to_string(), Dat::Str(fmt!("{}-{:02}-{:02}", self.year(), self.month(), self.day())));
500 doc.insert("time_str".to_string(), Dat::Str(fmt!("{:02}:{:02}:{:02}", self.hour(), self.minute(), self.second())));
501 if let Ok(iso_str) = self.to_iso8601() {
502 doc.insert("iso_string".to_string(), Dat::Str(iso_str));
503 }
504
505 // Flags
506 doc.insert("is_leap_second".to_string(), Dat::Bool(self.is_leap_second()));
507 doc.insert("is_leap_year".to_string(), Dat::Bool(self.is_leap_year()));
508 doc.insert("is_weekend".to_string(), Dat::Bool(self.date().is_weekend()));
509
510 doc
511 }
512
513 fn from_document(doc: &HashMap<String, Dat>) -> Outcome<Self> {
514 let year = res!(ok!(doc.get("year")
515 .ok_or_else(|| err!("Missing year in document"; Invalid, Input)))
516 .get_i32().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
517 let month = res!(ok!(doc.get("month")
518 .ok_or_else(|| err!("Missing month in document"; Invalid, Input)))
519 .get_u8().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
520 let day = res!(ok!(doc.get("day")
521 .ok_or_else(|| err!("Missing day in document"; Invalid, Input)))
522 .get_u8().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
523 let hour = res!(ok!(doc.get("hour")
524 .ok_or_else(|| err!("Missing hour in document"; Invalid, Input)))
525 .get_u8().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
526 let minute = res!(ok!(doc.get("minute")
527 .ok_or_else(|| err!("Missing minute in document"; Invalid, Input)))
528 .get_u8().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
529 let second = res!(ok!(doc.get("second")
530 .ok_or_else(|| err!("Missing second in document"; Invalid, Input)))
531 .get_u8().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
532 let nanosecond = res!(ok!(doc.get("nanosecond")
533 .ok_or_else(|| err!("Missing nanosecond in document"; Invalid, Input)))
534 .get_u32().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
535 let timezone_id = res!(ok!(doc.get("timezone")
536 .ok_or_else(|| err!("Missing timezone in document"; Invalid, Input)))
537 .get_string().ok_or_else(|| err!("Invalid type"; Invalid, Input)));
538
539 let zone = res!(CalClockZone::new(&timezone_id));
540 Self::new(year, month, day, hour, minute, second, nanosecond, zone)
541 }
542
543 fn index_fields() -> Vec<&'static str> {
544 vec![
545 "timestamp_nanos",
546 "timestamp_millis",
547 "year",
548 "month",
549 "day",
550 "hour",
551 "timezone",
552 "day_of_week",
553 "is_weekend",
554 "is_leap_second",
555 ]
556 }
557
558 fn compound_indexes() -> Vec<Vec<&'static str>> {
559 vec![
560 vec!["year", "month", "day"],
561 vec!["year", "month"],
562 vec!["timezone", "year"],
563 vec!["hour", "minute"],
564 vec!["day_of_week", "hour"],
565 vec!["timestamp_nanos", "timezone"],
566 vec!["is_weekend", "hour"],
567 ]
568 }
569}