Skip to content

Commit

Permalink
runtimes/js: support Date in sqldb
Browse files Browse the repository at this point in the history
  • Loading branch information
eandre committed Dec 6, 2024
1 parent 57e419d commit 7a86156
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 99 deletions.
15 changes: 15 additions & 0 deletions runtimes/core/src/api/pvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,18 @@ impl Display for PValue {
}
}
}

impl From<serde_json::Value> for PValue {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::Null => PValue::Null,
serde_json::Value::Bool(b) => PValue::Bool(b),
serde_json::Value::Number(n) => PValue::Number(n),
serde_json::Value::String(s) => PValue::String(s),
serde_json::Value::Array(a) => PValue::Array(a.into_iter().map(PValue::from).collect()),
serde_json::Value::Object(o) => {
PValue::Object(o.into_iter().map(|(k, v)| (k, PValue::from(v))).collect())
}
}
}
}
101 changes: 65 additions & 36 deletions runtimes/core/src/sqldb/val.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use anyhow::Context;
use bytes::BytesMut;
use bytes::{BufMut, BytesMut};
use serde::Serialize;
use std::error::Error;
use tokio_postgres::types::{FromSql, IsNull, Kind, ToSql, Type};
use tokio_postgres::types::{accepts, to_sql_checked, FromSql, IsNull, Kind, ToSql, Type};
use uuid::Uuid;

use crate::api::{DateTime, PValue};

#[derive(Debug)]
pub enum RowValue {
Json(serde_json::Value),
PVal(PValue),
Bytes(Vec<u8>),
Uuid(uuid::Uuid),
}
Expand All @@ -24,27 +27,25 @@ impl ToSql for RowValue {
_ => Err(format!("uuid not supported for column of type {}", ty).into()),
},

Self::Json(val) => match *ty {
Self::PVal(val) => match *ty {
Type::JSON | Type::JSONB => val.to_sql(ty, out),

_ => match val {
serde_json::Value::Null => Ok(IsNull::Yes),
serde_json::Value::Bool(bool) => match *ty {
PValue::Null => Ok(IsNull::Yes),
PValue::Bool(bool) => match *ty {
Type::BOOL => bool.to_sql(ty, out),
Type::TEXT | Type::VARCHAR => bool.to_string().to_sql(ty, out),
_ => Err(format!("bool not supported for column of type {}", ty).into()),
},

serde_json::Value::String(str) => match *ty {
PValue::String(str) => match *ty {
Type::TEXT | Type::VARCHAR => str.to_sql(ty, out),
Type::BYTEA => {
let val = str.as_bytes();
val.to_sql(ty, out)
}
Type::TIMESTAMP => {
let val =
chrono::NaiveDateTime::parse_from_str(str, "%Y-%m-%d %H:%M:%S")
.map_err(Box::new)?;
let val = str.parse::<chrono::NaiveDateTime>().map_err(Box::new)?;
val.to_sql(ty, out)
}
Type::TIMESTAMPTZ => {
Expand All @@ -69,7 +70,7 @@ impl ToSql for RowValue {
_ => Err(format!("string not supported for column of type {}", ty).into()),
},

serde_json::Value::Number(num) => match *ty {
PValue::Number(num) => match *ty {
Type::INT2 => {
let val: Result<i16, _> = if num.is_i64() {
num.as_i64().unwrap().try_into()
Expand Down Expand Up @@ -179,11 +180,11 @@ impl ToSql for RowValue {
}
}
},

serde_json::Value::Array(_) => {
PValue::DateTime(dt) => dt.to_sql(ty, out),
PValue::Array(_) => {
Err(format!("array not supported for column of type {}", ty).into())
}
serde_json::Value::Object(_) => {
PValue::Object(_) => {
Err(format!("object not supported for column of type {}", ty).into())
}
},
Expand Down Expand Up @@ -230,40 +231,40 @@ impl<'a> FromSql<'a> for RowValue {
Ok(match *ty {
Type::BOOL => {
let val: bool = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::Bool(val))
Self::PVal(PValue::Bool(val))
}
Type::BYTEA => {
let val: Vec<u8> = FromSql::from_sql(ty, raw)?;
Self::Bytes(val)
}
Type::TEXT | Type::VARCHAR => {
let val: String = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::String(val))
Self::PVal(PValue::String(val))
}

Type::INT2 => {
let val: i16 = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::Number(serde_json::Number::from(val)))
Self::PVal(PValue::Number(serde_json::Number::from(val)))
}
Type::INT4 => {
let val: i32 = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::Number(serde_json::Number::from(val)))
Self::PVal(PValue::Number(serde_json::Number::from(val)))
}
Type::INT8 => {
let val: i64 = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::Number(serde_json::Number::from(val)))
Self::PVal(PValue::Number(serde_json::Number::from(val)))
}
Type::OID => {
let val: u32 = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::Number(serde_json::Number::from(val)))
Self::PVal(PValue::Number(serde_json::Number::from(val)))
}
Type::JSON | Type::JSONB => {
let val: serde_json::Value = FromSql::from_sql(ty, raw)?;
Self::Json(val)
let val: PValue = FromSql::from_sql(ty, raw)?;
Self::PVal(val)
}
Type::JSON_ARRAY | Type::JSONB_ARRAY => {
let val: Vec<serde_json::Value> = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::Array(val))
let val: Vec<PValue> = FromSql::from_sql(ty, raw)?;
Self::PVal(PValue::Array(val))
}
Type::UUID => {
let val: uuid::Uuid = FromSql::from_sql(ty, raw)?;
Expand All @@ -273,40 +274,40 @@ impl<'a> FromSql<'a> for RowValue {
Type::FLOAT4 => {
let val: f32 = FromSql::from_sql(ty, raw)?;
match serde_json::Number::from_f64(val as f64) {
Some(num) => Self::Json(serde_json::Value::Number(num)),
None => Self::Json(serde_json::Value::Null),
Some(num) => Self::PVal(PValue::Number(num)),
None => Self::PVal(PValue::Null),
}
}

Type::FLOAT8 => {
let val: f64 = FromSql::from_sql(ty, raw)?;
match serde_json::Number::from_f64(val) {
Some(num) => Self::Json(serde_json::Value::Number(num)),
None => Self::Json(serde_json::Value::Null),
Some(num) => Self::PVal(PValue::Number(num)),
None => Self::PVal(PValue::Null),
}
}

Type::TIMESTAMP => {
let val: chrono::NaiveDateTime = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::String(val.to_string()))
let val: DateTime = FromSql::from_sql(ty, raw)?;
Self::PVal(PValue::DateTime(val))
}
Type::TIMESTAMPTZ => {
let val: chrono::DateTime<chrono::Utc> = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::String(val.to_string()))
let val: DateTime = FromSql::from_sql(ty, raw)?;
Self::PVal(PValue::DateTime(val))
}
Type::DATE => {
let val: chrono::NaiveDate = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::String(val.to_string()))
Self::PVal(PValue::String(val.to_string()))
}
Type::TIME => {
let val: chrono::NaiveTime = FromSql::from_sql(ty, raw)?;
Self::Json(serde_json::Value::String(val.to_string()))
Self::PVal(PValue::String(val.to_string()))
}

_ => {
if let Kind::Enum(_) = ty.kind() {
let val = std::str::from_utf8(raw)?;
Self::Json(serde_json::Value::String(val.to_string()))
Self::PVal(PValue::String(val.to_string()))
} else {
return Err(format!("unsupported type: {:?}", ty).into());
}
Expand All @@ -315,7 +316,7 @@ impl<'a> FromSql<'a> for RowValue {
}

fn from_sql_null(_: &Type) -> Result<Self, Box<dyn Error + Sync + Send>> {
Ok(Self::Json(serde_json::Value::Null))
Ok(Self::PVal(PValue::Null))
}

fn accepts(ty: &Type) -> bool {
Expand All @@ -341,3 +342,31 @@ impl<'a> FromSql<'a> for RowValue {
}
}
}

impl<'a> FromSql<'a> for PValue {
fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<PValue, Box<dyn Error + Sync + Send>> {
let val: serde_json::Value = FromSql::from_sql(ty, raw)?;
Ok(val.into())
}

accepts!(JSON, JSONB);
}

impl ToSql for PValue {
fn to_sql(
&self,
ty: &Type,
out: &mut BytesMut,
) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
if *ty == Type::JSONB {
out.put_u8(1);
}

let mut ser = serde_json::ser::Serializer::new(out.writer());
self.serialize(&mut ser)?;
Ok(IsNull::No)
}

accepts!(JSON, JSONB);
to_sql_checked!();
}
2 changes: 1 addition & 1 deletion runtimes/js/encore.dev/storage/sqldb/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const driverName = "node-pg";
export type Row = Record<string, any>;

/** Represents a type that can be used in query template literals */
export type Primitive = string | number | boolean | Buffer | null;
export type Primitive = string | number | boolean | Buffer | Date | null;

/**
* Constructing a new database object will result in Encore provisioning a database with
Expand Down
75 changes: 13 additions & 62 deletions runtimes/js/src/sqldb.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::api::Request;
use crate::pvalue::{parse_pvalue, pvalue_to_js};
use encore_runtime_core::sqldb;
use mappable_rc::Marc;
use napi::{Env, JsUnknown};
Expand All @@ -21,76 +22,26 @@ pub struct QueryArgs {
#[napi]
impl QueryArgs {
#[napi(constructor)]
pub fn new(env: Env, params: Vec<JsUnknown>) -> napi::Result<Self> {
let values = convert_row_values(env, params)?;
pub fn new(params: Vec<JsUnknown>) -> napi::Result<Self> {
let values = convert_row_values(params)?;
Ok(Self {
values: std::sync::Mutex::new(values),
})
}
}

fn convert_row_values(env: Env, params: Vec<JsUnknown>) -> napi::Result<Vec<sqldb::RowValue>> {
use napi::{JsBuffer, ValueType};
fn convert_row_values(params: Vec<JsUnknown>) -> napi::Result<Vec<sqldb::RowValue>> {
use napi::JsBuffer;
params
.into_iter()
.map(|val| -> napi::Result<sqldb::RowValue> {
Ok(match val.get_type()? {
ValueType::Null => sqldb::RowValue::Json(serde_json::Value::Null),
ValueType::Number => {
let float = val.coerce_to_number()?.get_double()?;
let int = float as i64;
if float == int as f64 {
sqldb::RowValue::Json(serde_json::Value::Number(int.into()))
} else {
match serde_json::Number::from_f64(float) {
Some(n) => sqldb::RowValue::Json(serde_json::Value::Number(n)),
None => {
return Err(napi::Error::new(
napi::Status::GenericFailure,
"failed to convert float to json number".to_string(),
));
}
}
}
}
ValueType::Boolean => {
let b = val.coerce_to_bool()?.get_value()?;
sqldb::RowValue::Json(serde_json::Value::Bool(b))
}
ValueType::String => {
let s = val.coerce_to_string()?.into_utf8()?.into_owned()?;
sqldb::RowValue::Json(serde_json::Value::String(s))
}
ValueType::Object => {
// Is this a buffer?
if val.is_buffer()? {
let buf: JsBuffer = val.try_into()?;
let buf = buf.into_value()?;
sqldb::RowValue::Bytes(buf.to_vec())
} else {
let val: serde_json::Value = env.from_js_value(val)?;
sqldb::RowValue::Json(val)
}
}
ValueType::Unknown => {
return Err(napi::Error::new(
napi::Status::GenericFailure,
"unknown not yet supported".to_string(),
));
}
ValueType::BigInt => {
return Err(napi::Error::new(
napi::Status::GenericFailure,
"unsupported value type".to_string(),
));
}
_ => {
return Err(napi::Error::new(
napi::Status::GenericFailure,
"unsupported value type".to_string(),
));
}
})
if val.is_buffer()? {
let buf: JsBuffer = val.try_into()?;
let buf = buf.into_value()?;
return Ok(sqldb::RowValue::Bytes(buf.to_vec()));
}
let pval = parse_pvalue(val)?;
Ok(sqldb::RowValue::PVal(pval))
})
.collect()
}
Expand Down Expand Up @@ -187,7 +138,7 @@ impl Row {
let mut map = HashMap::with_capacity(vals.len());
for (key, val) in vals {
let val: JsUnknown = match val {
sqldb::RowValue::Json(val) => env.to_js_value(&val)?,
sqldb::RowValue::PVal(val) => pvalue_to_js(env, &val)?,
sqldb::RowValue::Bytes(val) => {
env.create_arraybuffer_with_data(val)?.into_unknown()
}
Expand Down

0 comments on commit 7a86156

Please sign in to comment.