Skip to content

Commit ada52c8

Browse files
committed
Attemp sats deserialize
1 parent 32e5bc5 commit ada52c8

11 files changed

Lines changed: 604 additions & 433 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/src/subcommands/sql.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,8 @@ Roundtrip time: 1.00ms"#,
477477
vec![value],
478478
r#"
479479
column 0 | column 1 | column 2 | column 3 | column 4 | column 5
480-
----------+----------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------
481-
"a" | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000"#,
480+
----------+----------+--------------------------------------------------------------------+------------------------------------+---------------------------+----------
481+
"a" | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | P0D"#,
482482
);
483483

484484
// Check struct
@@ -508,8 +508,8 @@ Roundtrip time: 1.00ms"#,
508508
vec![value.clone()],
509509
r#"
510510
bool | str | bytes | identity | connection_id | timestamp | duration
511-
------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------
512-
true | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000"#,
511+
------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+----------
512+
true | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | P0D"#,
513513
);
514514

515515
// Check nested struct, tuple...
@@ -522,8 +522,8 @@ Roundtrip time: 1.00ms"#,
522522
vec![value.clone()],
523523
r#"
524524
column 0
525-
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
526-
(bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000)"#,
525+
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
526+
(bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = P0D)"#,
527527
);
528528

529529
let kind: ProductType = [("tuple", AlgebraicType::product(kind))].into();
@@ -535,8 +535,8 @@ Roundtrip time: 1.00ms"#,
535535
vec![value],
536536
r#"
537537
tuple
538-
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
539-
(0 = (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000))"#,
538+
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
539+
(col_0 = (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = P0D))"#,
540540
);
541541

542542
Ok(())

crates/pg/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,4 @@ rcgen.workspace = true
2323
rustls.workspace = true
2424
thiserror.workspace = true
2525
tokio.workspace = true
26-
tokio-rustls.workspace = true
27-
serde_json.workspace = true
26+
tokio-rustls.workspace = true

crates/pg/src/encoder.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
use crate::pg_server::PgError;
2+
use pgwire::api::portal::Format;
3+
use pgwire::api::results::{DataRowEncoder, FieldInfo};
4+
use pgwire::api::Type;
5+
use spacetimedb_lib::sats::satn::{PsqlPrintFmt, PsqlType, TypedWriter};
6+
use spacetimedb_lib::sats::{satn, ValueWithType};
7+
use spacetimedb_lib::{
8+
ser, AlgebraicType, AlgebraicValue, ProductType, ProductTypeElement, ProductValue, TimeDuration, Timestamp,
9+
};
10+
use std::borrow::Cow;
11+
use std::fmt::Display;
12+
use std::sync::Arc;
13+
14+
fn field_desc(schema: &ProductType, ty: &ProductTypeElement, format: &Format, idx: usize) -> FieldInfo {
15+
let field_name = ty.name.clone().map(Into::into).unwrap_or_else(|| format!("col {idx}"));
16+
let field_type = type_of(schema, ty);
17+
FieldInfo::new(field_name, None, None, field_type, format.format_for(idx))
18+
}
19+
pub(crate) fn row_desc(schema: &ProductType, format: &Format) -> Arc<Vec<FieldInfo>> {
20+
let mut field_info = Vec::with_capacity(schema.elements.len());
21+
for (idx, ty) in schema.elements.iter().enumerate() {
22+
field_info.push(field_desc(schema, ty, format, idx));
23+
}
24+
Arc::new(field_info)
25+
}
26+
27+
pub(crate) fn type_of(schema: &ProductType, ty: &ProductTypeElement) -> Type {
28+
let format = PsqlPrintFmt::use_fmt(schema, ty, ty.name());
29+
match &ty.algebraic_type {
30+
AlgebraicType::String => Type::VARCHAR,
31+
AlgebraicType::Bool => Type::BOOL,
32+
AlgebraicType::I8 | AlgebraicType::U8 | AlgebraicType::I16 => Type::INT2,
33+
AlgebraicType::U16 | AlgebraicType::I32 => Type::INT4,
34+
AlgebraicType::U32 | AlgebraicType::I64 => Type::INT8,
35+
AlgebraicType::U64 | AlgebraicType::I128 | AlgebraicType::U128 | AlgebraicType::I256 | AlgebraicType::U256 => {
36+
Type::NUMERIC
37+
}
38+
AlgebraicType::F32 => Type::FLOAT4,
39+
AlgebraicType::F64 => Type::FLOAT8,
40+
AlgebraicType::Array(ty) => match *ty.elem_ty {
41+
AlgebraicType::String => Type::VARCHAR_ARRAY,
42+
AlgebraicType::Bool => Type::BOOL_ARRAY,
43+
AlgebraicType::U8 => Type::BYTEA_ARRAY,
44+
AlgebraicType::I8 | AlgebraicType::I16 => Type::INT2_ARRAY,
45+
AlgebraicType::U16 | AlgebraicType::I32 => Type::INT4_ARRAY,
46+
AlgebraicType::U32 | AlgebraicType::I64 => Type::INT8_ARRAY,
47+
AlgebraicType::U64
48+
| AlgebraicType::I128
49+
| AlgebraicType::U128
50+
| AlgebraicType::I256
51+
| AlgebraicType::U256 => Type::NUMERIC_ARRAY,
52+
_ => Type::ANYARRAY,
53+
},
54+
AlgebraicType::Product(_) => match format {
55+
PsqlPrintFmt::Hex => Type::BYTEA_ARRAY,
56+
PsqlPrintFmt::Timestamp => Type::TIMESTAMP,
57+
PsqlPrintFmt::Duration => Type::INTERVAL,
58+
_ => Type::JSON,
59+
},
60+
x if x.as_sum().map(|x| x.is_simple_enum()).unwrap_or(false) => Type::ANYENUM,
61+
_ => Type::UNKNOWN,
62+
}
63+
}
64+
65+
impl ser::Error for PgError {
66+
fn custom<T: std::fmt::Display>(msg: T) -> Self {
67+
PgError::Other(anyhow::anyhow!(msg.to_string()))
68+
}
69+
}
70+
71+
pub(crate) struct PsqlFormatter<'a> {
72+
pub(crate) encoder: &'a mut DataRowEncoder,
73+
}
74+
75+
impl TypedWriter for PsqlFormatter<'_> {
76+
type Error = PgError;
77+
78+
fn write<W: Display>(&mut self, value: W) -> Result<(), Self::Error> {
79+
self.encoder.encode_field(&value.to_string())?;
80+
Ok(())
81+
}
82+
83+
fn write_bool(&mut self, value: bool) -> Result<(), Self::Error> {
84+
self.encoder.encode_field(&value)?;
85+
Ok(())
86+
}
87+
88+
fn write_string(&mut self, value: &str) -> Result<(), Self::Error> {
89+
self.encoder.encode_field(&value)?;
90+
Ok(())
91+
}
92+
93+
fn write_bytes(&mut self, value: &[u8]) -> Result<(), Self::Error> {
94+
self.encoder.encode_field(&value)?;
95+
Ok(())
96+
}
97+
98+
fn write_hex(&mut self, value: &[u8]) -> Result<(), Self::Error> {
99+
self.encoder.encode_field(&value)?;
100+
Ok(())
101+
}
102+
103+
fn write_timestamp(&mut self, value: Timestamp) -> Result<(), Self::Error> {
104+
self.encoder.encode_field(&value.to_rfc3339()?)?;
105+
Ok(())
106+
}
107+
108+
fn write_duration(&mut self, value: TimeDuration) -> Result<(), Self::Error> {
109+
self.encoder.encode_field(&value.to_iso8601())?;
110+
Ok(())
111+
}
112+
113+
fn write_alt_record(
114+
&mut self,
115+
ty: &PsqlType,
116+
value: &ValueWithType<'_, ProductValue>,
117+
) -> Result<bool, Self::Error> {
118+
let json = satn::PsqlWrapper { ty: ty.clone(), value }.to_string();
119+
self.encoder.encode_field(&json)?;
120+
Ok(true)
121+
}
122+
123+
fn write_record(
124+
&mut self,
125+
_fields: Vec<(Cow<str>, PsqlType, ValueWithType<AlgebraicValue>)>,
126+
) -> Result<(), Self::Error> {
127+
unreachable!()
128+
}
129+
130+
fn write_variant(
131+
&mut self,
132+
_tag: u8,
133+
ty: PsqlType,
134+
_name: Option<&str>,
135+
value: ValueWithType<AlgebraicValue>,
136+
) -> Result<(), Self::Error> {
137+
let json = satn::PsqlWrapper { ty, value }.to_string();
138+
self.encoder.encode_field(&json)?;
139+
Ok(())
140+
}
141+
}
142+
143+
// Tests
144+
#[cfg(test)]
145+
mod tests {
146+
use super::*;
147+
use crate::pg_server::to_rows;
148+
use futures::StreamExt;
149+
use spacetimedb_client_api_messages::http::SqlStmtResult;
150+
use spacetimedb_lib::sats::{product, AlgebraicType, ProductType};
151+
use spacetimedb_lib::{ConnectionId, Identity};
152+
153+
async fn run(schema: ProductType, row: ProductValue) -> String {
154+
let header = row_desc(&schema, &Format::UnifiedText);
155+
156+
let stmt = SqlStmtResult {
157+
schema,
158+
rows: vec![row],
159+
total_duration_micros: 0,
160+
stats: Default::default(),
161+
};
162+
let mut stream = to_rows(stmt, header).unwrap();
163+
let mut result = Vec::new();
164+
while let Some(row) = stream.next().await {
165+
result.push(String::from_utf8_lossy(row.unwrap().data.freeze().as_ref()).to_string());
166+
}
167+
assert_eq!(result.len(), 1);
168+
result[0].clone()
169+
}
170+
171+
#[tokio::test]
172+
async fn test_primitives() {
173+
let schema = ProductType::from([AlgebraicType::I64, AlgebraicType::String, AlgebraicType::U64]);
174+
let value = product![1i64, "test".to_string(), 2u64];
175+
176+
let row = run(schema, value).await;
177+
assert_eq!(row, "\0\0\0\u{1}1\0\0\0\u{4}test\0\0\0\u{1}2");
178+
}
179+
180+
#[tokio::test]
181+
async fn test_enum() {
182+
let schema = ProductType::from([AlgebraicType::option(AlgebraicType::I64)]);
183+
let value = product![
184+
AlgebraicValue::sum(0, AlgebraicValue::I64(1)), // Some(1)
185+
];
186+
187+
let row = run(schema, value).await;
188+
assert_eq!(row, "\0\0\0\u{1}1");
189+
}
190+
191+
#[tokio::test]
192+
async fn test_special_types() {
193+
let schema = ProductType::from([
194+
AlgebraicType::identity(),
195+
AlgebraicType::connection_id(),
196+
AlgebraicType::time_duration(),
197+
AlgebraicType::timestamp(),
198+
AlgebraicType::bytes(),
199+
]);
200+
let value = product![
201+
Identity::ZERO,
202+
ConnectionId::ZERO,
203+
TimeDuration::from_micros(0),
204+
Timestamp::from_micros_since_unix_epoch(1622545800000),
205+
AlgebraicValue::Bytes("test".as_bytes().into()),
206+
];
207+
208+
let row = run(schema, value).await;
209+
assert_eq!(row, "\0\0\0B\\x0000000000000000000000000000000000000000000000000000000000000000\0\0\0\"\\x00000000000000000000000000000000\0\0\0\u{3}P0D\0\0\0\u{1d}1970-01-19T18:42:25.800+00:00\0\0\0\n\\x74657374");
210+
}
211+
}

crates/pg/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
mod encoder;
12
pub mod pg_server;

0 commit comments

Comments
 (0)