gqlite 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +4 -3
  3. data/ext/gqlitedb/Cargo.toml +7 -4
  4. data/ext/gqlitedb/src/aggregators/arithmetic.rs +1 -0
  5. data/ext/gqlitedb/src/compiler/expression_analyser.rs +2 -0
  6. data/ext/gqlitedb/src/compiler.rs +31 -19
  7. data/ext/gqlitedb/src/connection.rs +42 -7
  8. data/ext/gqlitedb/src/error.rs +3 -0
  9. data/ext/gqlitedb/src/functions/containers.rs +1 -1
  10. data/ext/gqlitedb/src/functions/path.rs +1 -1
  11. data/ext/gqlitedb/src/functions/scalar.rs +23 -0
  12. data/ext/gqlitedb/src/functions.rs +8 -0
  13. data/ext/gqlitedb/src/interpreter/evaluators.rs +10 -10
  14. data/ext/gqlitedb/src/interpreter/instructions.rs +3 -3
  15. data/ext/gqlitedb/src/lib.rs +1 -3
  16. data/ext/gqlitedb/src/parser/ast.rs +1 -0
  17. data/ext/gqlitedb/src/parser/gql.pest +3 -1
  18. data/ext/gqlitedb/src/parser/parser_impl.rs +8 -0
  19. data/ext/gqlitedb/src/prelude.rs +3 -0
  20. data/ext/gqlitedb/src/store/{pgql.rs → pgrx.rs} +2 -0
  21. data/ext/gqlitedb/src/store/postgres.rs +0 -0
  22. data/ext/gqlitedb/src/store/sqlbase/sqlmetadata.rs +117 -0
  23. data/ext/gqlitedb/src/store/sqlbase/sqlqueries.rs +62 -0
  24. data/ext/gqlitedb/src/store/sqlbase/sqlstore.rs +55 -0
  25. data/ext/gqlitedb/src/store/sqlbase/sqlvalue.rs +189 -0
  26. data/ext/gqlitedb/src/store/sqlbase.rs +456 -0
  27. data/ext/gqlitedb/src/store/sqlite.rs +271 -573
  28. data/ext/gqlitedb/src/store.rs +7 -5
  29. data/ext/gqlitedb/src/tests/templates/programs.rs +10 -10
  30. data/ext/gqlitedb/src/utils.rs +25 -0
  31. data/ext/gqlitedb/src/value/compare.rs +6 -0
  32. data/ext/gqlitedb/src/value.rs +18 -2
  33. data/ext/gqlitedb/templates/sql/sqlite/edge_select.sql +18 -18
  34. data/ext/gqlitedb/templates/sql/sqlite/edge_update.sql +3 -3
  35. data/ext/gqlitedb/templates/sql/sqlite/node_select.sql +6 -6
  36. data/ext/gqlitedb/templates/sql/sqlite/node_update.sql +3 -3
  37. data/ext/gqliterb/src/lib.rs +30 -2
  38. data/ext/graphcore/Cargo.toml +3 -2
  39. data/ext/graphcore/src/error.rs +2 -0
  40. data/ext/graphcore/src/lib.rs +2 -1
  41. data/ext/graphcore/src/prelude.rs +1 -1
  42. data/ext/graphcore/src/table.rs +1 -1
  43. data/ext/graphcore/src/timestamp.rs +104 -0
  44. data/ext/graphcore/src/value.rs +106 -23
  45. metadata +10 -7
  46. data/ext/gqlitedb/gqlite_bench_data/README.MD +0 -6
  47. data/ext/gqlitedb/gqlite_bench_data/scripts/generate_smaller_pokec.rb +0 -85
  48. data/ext/gqlitedb/gqlite_bench_data/scripts/to_efficient_pokec.rb +0 -34
  49. data/ext/graphcore/release.toml +0 -1
@@ -1,13 +1,15 @@
1
- #[cfg(feature = "_pgql")]
2
- pub(crate) mod pgql;
1
+ #[cfg(any(feature = "sqlite", feature = "postgres", feature = "_pgrx"))]
2
+ pub(crate) mod sqlbase;
3
+
4
+ #[cfg(feature = "_pgrx")]
5
+ pub(crate) mod pgrx;
6
+ #[cfg(feature = "postgres")]
7
+ pub(crate) mod postgres;
3
8
  #[cfg(feature = "redb")]
4
9
  pub(crate) mod redb;
5
10
  #[cfg(feature = "sqlite")]
6
11
  pub(crate) mod sqlite;
7
12
 
8
- #[cfg(feature = "_pgql")]
9
- pub(crate) use pgql::Store;
10
-
11
13
  use crate::prelude::*;
12
14
 
13
15
  // ____ _ _ _ _ _
@@ -71,7 +71,7 @@ pub(crate) fn create_named_node() -> Program
71
71
  skip: None,
72
72
  order_by: vec![],
73
73
  },
74
- variables_size: create_variable_size(2, 0),
74
+ variables_sizes: create_variable_size(2, 0),
75
75
  },
76
76
  ]
77
77
  }
@@ -133,7 +133,7 @@ pub(crate) fn create_named_node_double_return() -> Program
133
133
  skip: None,
134
134
  order_by: vec![],
135
135
  },
136
- variables_size: create_variable_size(3, 0),
136
+ variables_sizes: create_variable_size(3, 0),
137
137
  },
138
138
  ]
139
139
  }
@@ -161,7 +161,7 @@ pub(crate) fn double_with_return() -> Program
161
161
  skip: None,
162
162
  order_by: vec![],
163
163
  },
164
- variables_size: create_variable_size(2, 0),
164
+ variables_sizes: create_variable_size(2, 0),
165
165
  },
166
166
  Block::With {
167
167
  variables: vec![
@@ -182,7 +182,7 @@ pub(crate) fn double_with_return() -> Program
182
182
  skip: None,
183
183
  order_by: vec![],
184
184
  },
185
- variables_size: create_variable_size(4, 0),
185
+ variables_sizes: create_variable_size(4, 0),
186
186
  },
187
187
  Block::Return {
188
188
  variables: vec![(
@@ -199,7 +199,7 @@ pub(crate) fn double_with_return() -> Program
199
199
  skip: None,
200
200
  order_by: vec![],
201
201
  },
202
- variables_size: create_variable_size(2, 0),
202
+ variables_sizes: create_variable_size(2, 0),
203
203
  },
204
204
  ]
205
205
  }
@@ -234,7 +234,7 @@ pub(crate) fn unwind() -> Program
234
234
  skip: None,
235
235
  order_by: vec![],
236
236
  },
237
- variables_size: create_variable_size(1, 0),
237
+ variables_sizes: create_variable_size(1, 0),
238
238
  },
239
239
  ]
240
240
  }
@@ -288,7 +288,7 @@ pub(crate) fn match_loop() -> Program
288
288
  skip: None,
289
289
  order_by: vec![],
290
290
  },
291
- variables_size: create_variable_size(1, 0),
291
+ variables_sizes: create_variable_size(1, 0),
292
292
  },
293
293
  ]
294
294
  }
@@ -327,7 +327,7 @@ pub(crate) fn optional_match() -> Program
327
327
  skip: None,
328
328
  order_by: vec![],
329
329
  },
330
- variables_size: create_variable_size(1, 0),
330
+ variables_sizes: create_variable_size(1, 0),
331
331
  },
332
332
  ]
333
333
  }
@@ -378,7 +378,7 @@ pub(crate) fn match_count(function_manager: &functions::Manager) -> Program
378
378
  skip: None,
379
379
  order_by: vec![],
380
380
  },
381
- variables_size: create_variable_size(2, 1),
381
+ variables_sizes: create_variable_size(2, 1),
382
382
  },
383
383
  ]
384
384
  }
@@ -449,7 +449,7 @@ pub(crate) fn aggregation(function_manager: &functions::Manager) -> Program
449
449
  skip: None,
450
450
  order_by: vec![],
451
451
  },
452
- variables_size: create_variable_size(3, 1),
452
+ variables_sizes: create_variable_size(3, 1),
453
453
  },
454
454
  ]
455
455
  }
@@ -2,6 +2,8 @@ use std::fmt::{Debug, Display};
2
2
 
3
3
  use serde::{Deserialize, Serialize};
4
4
 
5
+ use crate::prelude::*;
6
+
5
7
  #[derive(Serialize, Deserialize)]
6
8
  pub struct Version
7
9
  {
@@ -26,3 +28,26 @@ impl Display for Version
26
28
  f.write_fmt(format_args!("{}.{}.{}", self.major, self.minor, self.patch))
27
29
  }
28
30
  }
31
+
32
+ pub(crate) fn hex(key: impl Into<graph::Key>) -> String
33
+ {
34
+ format!("{:032X}", key.into().uuid())
35
+ }
36
+
37
+ #[cfg(test)]
38
+ mod tests
39
+ {
40
+ use crate::prelude::*;
41
+ #[test]
42
+ fn test_hex()
43
+ {
44
+ assert_eq!(
45
+ super::hex(graph::Key::new(18580062510968287067562660977870108180)),
46
+ "0DFA63CEE7484B0DBFC407697F77F614"
47
+ );
48
+ assert_eq!(
49
+ super::hex(graph::Key::new(0)),
50
+ "00000000000000000000000000000000"
51
+ );
52
+ }
53
+ }
@@ -111,6 +111,12 @@ pub(crate) fn compare(lhs: &value::Value, rhs: &value::Value) -> Ordering
111
111
  Value::Null => Ordering::ComparedNull,
112
112
  _ => Ordering::Null,
113
113
  },
114
+ Value::TimeStamp(tl) => match rhs
115
+ {
116
+ Value::TimeStamp(tr) => tl.cmp(tr).into(),
117
+ Value::Null => Ordering::ComparedNull,
118
+ _ => Ordering::Null,
119
+ },
114
120
  Value::Array(al) => match rhs
115
121
  {
116
122
  Value::Array(ar) =>
@@ -4,7 +4,7 @@ mod contains;
4
4
  pub(crate) use compare::{compare, Ordering};
5
5
  pub(crate) use contains::{contains, ContainResult};
6
6
 
7
- pub use graphcore::{array, value_map, Value, ValueMap, ValueTryIntoRef};
7
+ pub use graphcore::{array, value_map, TimeStamp, Value, ValueMap, ValueTryIntoRef};
8
8
 
9
9
  pub(crate) trait ValueExt
10
10
  {
@@ -134,7 +134,7 @@ impl ValueExt for Value
134
134
  Value::String(rhs) => lhs.cmp(rhs),
135
135
  _ => std::cmp::Ordering::Greater,
136
136
  },
137
- Value::Path(lhs) => match rhs
137
+ Value::TimeStamp(lhs) => match rhs
138
138
  {
139
139
  Value::Null
140
140
  | Value::Key(..)
@@ -142,6 +142,18 @@ impl ValueExt for Value
142
142
  | Value::Float(..)
143
143
  | Value::Boolean(..)
144
144
  | Value::String(..) => std::cmp::Ordering::Less,
145
+ Value::TimeStamp(rhs) => lhs.cmp(rhs),
146
+ _ => std::cmp::Ordering::Greater,
147
+ },
148
+ Value::Path(lhs) => match rhs
149
+ {
150
+ Value::Null
151
+ | Value::Key(..)
152
+ | Value::Integer(..)
153
+ | Value::Float(..)
154
+ | Value::Boolean(..)
155
+ | Value::String(..)
156
+ | Value::TimeStamp(..) => std::cmp::Ordering::Less,
145
157
  Value::Path(rhs) =>
146
158
  {
147
159
  match orderability_map(lhs.source().properties(), rhs.source().properties())
@@ -170,6 +182,7 @@ impl ValueExt for Value
170
182
  | Value::Float(..)
171
183
  | Value::Boolean(..)
172
184
  | Value::String(..)
185
+ | Value::TimeStamp(..)
173
186
  | Value::Path(..) => std::cmp::Ordering::Less,
174
187
  Value::Array(rhs) => lhs
175
188
  .iter()
@@ -187,6 +200,7 @@ impl ValueExt for Value
187
200
  | Value::Float(..)
188
201
  | Value::Boolean(..)
189
202
  | Value::String(..)
203
+ | Value::TimeStamp(..)
190
204
  | Value::Path(..)
191
205
  | Value::Array(..) => std::cmp::Ordering::Less,
192
206
  Value::Edge(rhs) => orderability_map(lhs.properties(), rhs.properties()),
@@ -200,6 +214,7 @@ impl ValueExt for Value
200
214
  | Value::Float(..)
201
215
  | Value::Boolean(..)
202
216
  | Value::String(..)
217
+ | Value::TimeStamp(..)
203
218
  | Value::Path(..)
204
219
  | Value::Array(..)
205
220
  | Value::Edge(..) => std::cmp::Ordering::Less,
@@ -214,6 +229,7 @@ impl ValueExt for Value
214
229
  | Value::Float(..)
215
230
  | Value::Boolean(..)
216
231
  | Value::String(..)
232
+ | Value::TimeStamp(..)
217
233
  | Value::Path(..)
218
234
  | Value::Array(..)
219
235
  | Value::Edge(..)
@@ -17,22 +17,22 @@ JOIN gqlite_{{ graph_name }}_nodes AS n_left ON e.left = n_left.id
17
17
  JOIN gqlite_{{ graph_name }}_nodes AS n_right ON e.right = n_right.id
18
18
  WHERE
19
19
  -- Filter by key list (if not empty)
20
- {% if has_edge_keys %}
20
+ {% if let Some(edge_keys_var) = edge_keys_var %}
21
21
  (
22
22
  hex(e.edge_key) IN (
23
- SELECT value FROM json_each(:edge_keys)
23
+ SELECT value FROM json_each(?{{ edge_keys_var }})
24
24
  )
25
25
  )
26
26
  {% else %}
27
27
  1
28
28
  {% endif %}
29
29
  AND
30
- {% if has_edge_labels %}
30
+ {% if let Some(edge_labels_var) = edge_labels_var %}
31
31
  -- Filter by required labels (must all be in e.labels)
32
32
  (
33
33
  NOT EXISTS (
34
34
  SELECT 1
35
- FROM json_each(:edge_labels) AS required_label
35
+ FROM json_each(?{{ edge_labels_var }}) AS required_label
36
36
  WHERE NOT EXISTS (
37
37
  SELECT 1
38
38
  FROM json_each(e.labels) AS edge_label
@@ -44,11 +44,11 @@ WHERE
44
44
  1
45
45
  {% endif %}
46
46
  AND
47
- {% if has_edge_properties %}
47
+ {% if let Some(edge_properties_var) = edge_properties_var %}
48
48
  -- Filter by required properties (must all exist and match)
49
49
  NOT EXISTS (
50
50
  SELECT 1
51
- FROM json_each(:edge_properties) AS required_prop
51
+ FROM json_each(?{{ edge_properties_var }}) AS required_prop
52
52
  WHERE json_extract(e.properties, '$.' || required_prop.key) IS NULL
53
53
  OR json_extract(e.properties, '$.' || required_prop.key) != required_prop.value
54
54
  )
@@ -57,22 +57,22 @@ WHERE
57
57
  {% endif %}
58
58
  -- Filter by key list (if not empty)
59
59
  AND
60
- {% if has_n_left_keys %}
60
+ {% if let Some(left_keys_var) = left_keys_var %}
61
61
  (
62
62
  hex(n_left.node_key) IN (
63
- SELECT value FROM json_each(:n_left_keys)
63
+ SELECT value FROM json_each(?{{ left_keys_var }})
64
64
  )
65
65
  )
66
66
  {% else %}
67
67
  1
68
68
  {% endif %}
69
69
  AND
70
- {% if has_n_left_labels %}
70
+ {% if let Some(left_labels_var) = left_labels_var %}
71
71
  -- Filter by required labels (must all be in n_left.labels)
72
72
  (
73
73
  NOT EXISTS (
74
74
  SELECT 1
75
- FROM json_each(:n_left_labels) AS required_label
75
+ FROM json_each(?{{ left_labels_var }}) AS required_label
76
76
  WHERE NOT EXISTS (
77
77
  SELECT 1
78
78
  FROM json_each(n_left.labels) AS node_label
@@ -84,12 +84,12 @@ WHERE
84
84
  1
85
85
  {% endif %}
86
86
  AND
87
- {% if has_n_left_properties %}
87
+ {% if let Some(left_properties_var) = left_properties_var %}
88
88
  -- Filter by required properties (must all exist and match)
89
89
  (
90
90
  NOT EXISTS (
91
91
  SELECT 1
92
- FROM json_each(:n_left_properties) AS required_prop
92
+ FROM json_each(?{{ left_properties_var }}) AS required_prop
93
93
  WHERE json_extract(n_left.properties, '$.' || required_prop.key) IS NULL
94
94
  OR json_extract(n_left.properties, '$.' || required_prop.key) != required_prop.value
95
95
  )
@@ -99,22 +99,22 @@ WHERE
99
99
  {% endif %}
100
100
  AND
101
101
  -- Filter by key list (if not empty)
102
- {% if has_n_right_keys %}
102
+ {% if let Some(right_keys_var) = right_keys_var %}
103
103
  (
104
104
  hex(n_right.node_key) IN (
105
- SELECT value FROM json_each(:n_right_keys)
105
+ SELECT value FROM json_each(?{{ right_keys_var }})
106
106
  )
107
107
  )
108
108
  {% else %}
109
109
  1
110
110
  {% endif %}
111
111
  AND
112
- {% if has_n_right_labels %}
112
+ {% if let Some(right_labels_var) = right_labels_var %}
113
113
  -- Filter by required labels (must all be in n_right.labels)
114
114
  (
115
115
  NOT EXISTS (
116
116
  SELECT 1
117
- FROM json_each(:n_right_labels) AS required_label
117
+ FROM json_each(?{{ right_labels_var }}) AS required_label
118
118
  WHERE NOT EXISTS (
119
119
  SELECT 1
120
120
  FROM json_each(n_right.labels) AS node_label
@@ -126,12 +126,12 @@ WHERE
126
126
  1
127
127
  {% endif %}
128
128
 
129
- {% if has_n_right_properties %}
129
+ {% if let Some(right_properties_var) = right_properties_var %}
130
130
  -- Filter by required properties (must all exist and match)
131
131
  AND (
132
132
  NOT EXISTS (
133
133
  SELECT 1
134
- FROM json_each(:n_right_properties) AS required_prop
134
+ FROM json_each(?{{ right_properties_var }}) AS required_prop
135
135
  WHERE json_extract(n_right.properties, '$.' || required_prop.key) IS NULL
136
136
  OR json_extract(n_right.properties, '$.' || required_prop.key) != required_prop.value
137
137
  )
@@ -1,4 +1,4 @@
1
1
  UPDATE gqlite_{{ graph_name }}_edges
2
- SET labels = :labels,
3
- properties = :properties
4
- WHERE edge_key = :key;
2
+ SET labels = ?2,
3
+ properties = ?3
4
+ WHERE edge_key = ?1;
@@ -2,22 +2,22 @@ SELECT node_key, labels, properties
2
2
  FROM gqlite_{{ graph_name }}_nodes AS nodes
3
3
  WHERE
4
4
  -- Filter by key list (if not empty)
5
- {% if has_keys %}
5
+ {% if let Some(keys_var) = keys_var %}
6
6
  (
7
7
  hex(nodes.node_key) IN (
8
- SELECT value FROM json_each(:keys)
8
+ SELECT value FROM json_each(?{{ keys_var }})
9
9
  )
10
10
  )
11
11
  {% else %}
12
12
  1
13
13
  {% endif %}
14
14
  AND
15
- {% if has_labels %}
15
+ {% if let Some(labels_var) = labels_var %}
16
16
  -- Filter by required labels (must all be in nodes.labels)
17
17
  (
18
18
  NOT EXISTS (
19
19
  SELECT 1
20
- FROM json_each(:labels) AS required_label
20
+ FROM json_each(?{{ labels_var }}) AS required_label
21
21
  WHERE NOT EXISTS (
22
22
  SELECT 1
23
23
  FROM json_each(nodes.labels) AS node_label
@@ -29,12 +29,12 @@ WHERE
29
29
  1
30
30
  {% endif %}
31
31
 
32
- {% if has_properties %}
32
+ {% if let Some(properties_var) = properties_var %}
33
33
  -- Filter by required properties (must all exist and match)
34
34
  AND (
35
35
  NOT EXISTS (
36
36
  SELECT 1
37
- FROM json_each(:properties) AS required_prop
37
+ FROM json_each(?{{ properties_var }}) AS required_prop
38
38
  WHERE json_extract(nodes.properties, '$.' || required_prop.key) IS NULL
39
39
  OR json_extract(nodes.properties, '$.' || required_prop.key) != required_prop.value
40
40
  )
@@ -1,4 +1,4 @@
1
1
  UPDATE gqlite_{{ graph_name }}_nodes
2
- SET labels = :labels,
3
- properties = :properties
4
- WHERE node_key = :key;
2
+ SET labels = ?2,
3
+ properties = ?3
4
+ WHERE node_key = ?1;
@@ -1,7 +1,6 @@
1
- #![deny(warnings)]
2
-
3
1
  use std::fmt::Display;
4
2
 
3
+ use gqlitedb::TimeStamp;
5
4
  use magnus::{
6
5
  function, method,
7
6
  prelude::*,
@@ -47,6 +46,22 @@ fn from_rvalue(ruby: &Ruby, value: magnus::Value) -> Result<gqlitedb::Value, Err
47
46
  {
48
47
  Ok(String::try_convert(value)?.into())
49
48
  }
49
+ else if value.is_kind_of(ruby.class_time())
50
+ {
51
+ let time = magnus::Time::try_convert(value)?;
52
+ let timespec = time.timespec()?;
53
+ Ok(
54
+ map_err(
55
+ ruby,
56
+ TimeStamp::from_unix_timestamp(
57
+ timespec.tv_sec,
58
+ timespec.tv_nsec as i32,
59
+ time.utc_offset() as i32,
60
+ ),
61
+ )?
62
+ .into(),
63
+ )
64
+ }
50
65
  else if value.is_kind_of(ruby.class_hash())
51
66
  {
52
67
  Ok(from_rhash(ruby, r_hash::RHash::try_convert(value)?)?.into())
@@ -152,6 +167,7 @@ fn to_rvalue(ruby: &Ruby, val: gqlitedb::Value) -> Result<magnus::Value, Error>
152
167
  gqlitedb::Value::Integer(i) => Ok(i.into_value_with(ruby)),
153
168
  gqlitedb::Value::Float(f) => Ok(f.into_value_with(ruby)),
154
169
  gqlitedb::Value::String(s) => Ok(s.into_value_with(ruby)),
170
+ gqlitedb::Value::TimeStamp(s) => Ok(to_rdatetime(ruby, s)?.into_value_with(ruby)),
155
171
  gqlitedb::Value::Map(m) => Ok(to_rhash(ruby, m)?.into_value_with(ruby)),
156
172
  gqlitedb::Value::Null => Ok(ruby.qnil().into_value_with(ruby)),
157
173
  gqlitedb::Value::Edge(e) => Ok(edge_to_rhash(ruby, e)?),
@@ -160,6 +176,18 @@ fn to_rvalue(ruby: &Ruby, val: gqlitedb::Value) -> Result<magnus::Value, Error>
160
176
  }
161
177
  }
162
178
 
179
+ fn to_rdatetime(ruby: &Ruby, ts: gqlitedb::TimeStamp) -> Result<magnus::Time, Error>
180
+ {
181
+ let (tv_sec, tv_nsec) = ts.unix_timestamp();
182
+ ruby.time_timespec_new(
183
+ magnus::time::Timespec {
184
+ tv_sec,
185
+ tv_nsec: tv_nsec as i64,
186
+ },
187
+ map_err(ruby, magnus::time::Offset::from_secs(ts.offset_seconds()))?,
188
+ )
189
+ }
190
+
163
191
  fn to_rhash(ruby: &Ruby, map: gqlitedb::ValueMap) -> Result<r_hash::RHash, Error>
164
192
  {
165
193
  let r_hash = ruby.hash_new();
@@ -1,16 +1,17 @@
1
1
  [package]
2
2
  name = "graphcore"
3
3
  description = "Base data structure to represent and manipulate property graph."
4
- # version.workspace = true
5
- version = "0.2.0"
6
4
  readme = "README.MD"
5
+ version.workspace = true
7
6
  edition.workspace = true
8
7
  license.workspace = true
9
8
  homepage.workspace = true
10
9
  repository.workspace = true
11
10
 
12
11
  [dependencies]
12
+ ccutils = { workspace = true, features = ["alias"] }
13
13
  itertools = { workspace = true }
14
+ jiff = { version = "0.2" }
14
15
  serde = { workspace = true, features = ["derive"] }
15
16
  thiserror = { workspace = true }
16
17
  uuid = { workspace = true }
@@ -25,4 +25,6 @@ pub enum Error
25
25
  InvalidTableDimensions,
26
26
  #[error("Out of range access.")]
27
27
  InvalidRange,
28
+ #[error("TimeStamp error {0}.")]
29
+ TimeError(#[from] jiff::Error),
28
30
  }
@@ -1,6 +1,5 @@
1
1
  #![doc = include_str!("../README.MD")]
2
2
  #![warn(missing_docs)]
3
- #![deny(warnings)]
4
3
  #![allow(clippy::result_large_err)]
5
4
 
6
5
  mod error;
@@ -8,9 +7,11 @@ mod graph;
8
7
  mod prelude;
9
8
  mod serialize_with;
10
9
  mod table;
10
+ mod timestamp;
11
11
  mod value;
12
12
 
13
13
  pub use error::Error;
14
14
  pub use graph::{Edge, Key, Node, SinglePath};
15
15
  pub use table::Table;
16
+ pub use timestamp::TimeStamp;
16
17
  pub use value::{Value, ValueMap, ValueTryIntoRef};
@@ -1,4 +1,4 @@
1
- pub(crate) use crate::{error, graph, serialize_with, value};
1
+ pub(crate) use crate::{error, graph, serialize_with, timestamp, value};
2
2
  pub(crate) use error::Error;
3
3
 
4
4
  pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -15,7 +15,7 @@ impl Table
15
15
  /// of row is equal to length of data divided by number of columns.
16
16
  pub fn new(headers: Vec<String>, data: Vec<crate::Value>) -> Result<Table>
17
17
  {
18
- if data.len() % headers.len() != 0
18
+ if !data.len().is_multiple_of(headers.len())
19
19
  {
20
20
  Err(error::Error::InvalidTableDimensions)?;
21
21
  }
@@ -0,0 +1,104 @@
1
+ use std::fmt;
2
+
3
+ use jiff::{tz::TimeZone, Zoned};
4
+ use serde::{Deserialize, Serialize};
5
+
6
+ ccutils::alias!(#[doc = "Represent a timestamp"] pub TimeStamp, Zoned, display, derive: Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord);
7
+
8
+ impl TimeStamp
9
+ {
10
+ /// Create a new time stamp with the given unix timestamp and offset
11
+ pub fn from_unix_timestamp(
12
+ seconds: i64,
13
+ nanoseconds: i32,
14
+ offset_whole_seconds: i32,
15
+ ) -> Result<TimeStamp, crate::Error>
16
+ {
17
+ let ts = jiff::Timestamp::new(seconds, nanoseconds)?;
18
+ Ok(Self(ts.to_zoned(jiff::tz::TimeZone::fixed(
19
+ jiff::tz::Offset::from_seconds(offset_whole_seconds)?,
20
+ ))))
21
+ }
22
+
23
+ /// Convert from the current time zone to Utc (aka offset == 0)
24
+ pub fn to_utc(&self) -> TimeStamp
25
+ {
26
+ Self(self.with_time_zone(TimeZone::UTC))
27
+ }
28
+ /// Return the unix timestamp
29
+ pub fn unix_timestamp(&self) -> (i64, i32)
30
+ {
31
+ let ts = self.0.timestamp();
32
+ (ts.as_second(), ts.subsec_nanosecond())
33
+ }
34
+ /// Return the number of whole seconds in the offset
35
+ pub fn offset_seconds(&self) -> i32
36
+ {
37
+ self.0.offset().seconds()
38
+ }
39
+ /// Return the year in the timestamp timezone
40
+ pub fn year(&self) -> i16
41
+ {
42
+ self.0.year()
43
+ }
44
+ /// Return the month in the timestamp timezone
45
+ pub fn month(&self) -> i8
46
+ {
47
+ self.0.month()
48
+ }
49
+ /// Return the day in the timestamp timezone
50
+ pub fn day(&self) -> i8
51
+ {
52
+ self.0.day()
53
+ }
54
+ /// Return the hour in the timestamp timezone
55
+ pub fn hour(&self) -> i8
56
+ {
57
+ self.0.hour()
58
+ }
59
+ /// Return the minute in the timestamp timezone
60
+ pub fn minute(&self) -> i8
61
+ {
62
+ self.0.minute()
63
+ }
64
+ /// Return the second in the timestamp timezone
65
+ pub fn second(&self) -> i8
66
+ {
67
+ self.0.second()
68
+ }
69
+ /// Return the microseconds in the timestamp timezone
70
+ pub fn microsecond(&self) -> i16
71
+ {
72
+ self.0.microsecond()
73
+ }
74
+ /// Parse the time string
75
+ pub fn parse(date: &str) -> Result<TimeStamp, crate::Error>
76
+ {
77
+ Ok(TimeStamp(date.parse()?))
78
+ }
79
+ }
80
+
81
+ // Serde impl
82
+
83
+ impl Serialize for TimeStamp
84
+ {
85
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86
+ where
87
+ S: serde::Serializer,
88
+ {
89
+ self.0.to_string().serialize(serializer)
90
+ }
91
+ }
92
+
93
+ impl<'de> Deserialize<'de> for TimeStamp
94
+ {
95
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96
+ where
97
+ D: serde::Deserializer<'de>,
98
+ {
99
+ use serde::de::Error;
100
+ let s = String::deserialize(deserializer)?;
101
+ TimeStamp::parse(&s)
102
+ .map_err(|e| D::Error::custom(format!("Error deserializing timestamp: {}.", e)))
103
+ }
104
+ }