Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5.7.x #4587

Closed
wants to merge 4 commits into from
Closed

5.7.x #4587

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions nix/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ in
nodejs.pkgs.typescript-language-server
nodejs.pkgs.pnpm

cargo-insta
jq
graphviz
wasm-bindgen-cli
Expand Down
2 changes: 2 additions & 0 deletions psl/builtin-connectors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ indoc.workspace = true
lsp-types = "0.91.1"
once_cell = "1.3"
regex = "1"
chrono = { version = "0.4.6", default-features = false }

24 changes: 24 additions & 0 deletions psl/builtin-connectors/src/cockroach_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod validations;

pub use native_types::CockroachType;

use chrono::*;
use enumflags2::BitFlags;
use lsp_types::{CompletionItem, CompletionItemKind, CompletionList};
use psl_core::{
Expand Down Expand Up @@ -307,6 +308,29 @@ impl Connector for CockroachDatamodelConnector {
fn flavour(&self) -> Flavour {
Flavour::Cockroach
}

fn parse_json_datetime(
&self,
str: &str,
nt: Option<NativeTypeInstance>,
) -> chrono::ParseResult<chrono::DateTime<FixedOffset>> {
let native_type: Option<&CockroachType> = nt.as_ref().map(|nt| nt.downcast_ref());

match native_type {
Some(ct) => match ct {
CockroachType::Timestamptz(_) => crate::utils::parse_timestamptz(str),
CockroachType::Timestamp(_) => crate::utils::parse_timestamp(str),
CockroachType::Date => crate::utils::parse_date(str),
CockroachType::Time(_) => crate::utils::parse_time(str),
CockroachType::Timetz(_) => crate::utils::parse_timetz(str),
_ => unreachable!(),
},
None => self.parse_json_datetime(
str,
Some(self.default_native_type_for_scalar_type(&ScalarType::DateTime)),
),
}
}
}

/// An `@default(sequence())` function.
Expand Down
1 change: 1 addition & 0 deletions psl/builtin-connectors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod mysql_datamodel_connector;
mod native_type_definition;
mod postgres_datamodel_connector;
mod sqlite_datamodel_connector;
mod utils;

use psl_core::{datamodel_connector::Connector, ConnectorRegistry};

Expand Down
24 changes: 24 additions & 0 deletions psl/builtin-connectors/src/postgres_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod validations;

pub use native_types::PostgresType;

use chrono::*;
use enumflags2::BitFlags;
use lsp_types::{CompletionItem, CompletionItemKind, CompletionList, InsertTextFormat};
use psl_core::{
Expand Down Expand Up @@ -567,6 +568,29 @@ impl Connector for PostgresDatamodelConnector {
fn flavour(&self) -> Flavour {
Flavour::Postgres
}

fn parse_json_datetime(
&self,
str: &str,
nt: Option<NativeTypeInstance>,
) -> chrono::ParseResult<chrono::DateTime<FixedOffset>> {
let native_type: Option<&PostgresType> = nt.as_ref().map(|nt| nt.downcast_ref());

match native_type {
Some(pt) => match pt {
Timestamptz(_) => crate::utils::parse_timestamptz(str),
Timestamp(_) => crate::utils::parse_timestamp(str),
Date => crate::utils::parse_date(str),
Time(_) => crate::utils::parse_time(str),
Timetz(_) => crate::utils::parse_timetz(str),
_ => unreachable!(),
},
None => self.parse_json_datetime(
str,
Some(self.default_native_type_for_scalar_type(&ScalarType::DateTime)),
),
}
}
}

fn allowed_index_operator_classes(algo: IndexAlgorithm, field: walkers::ScalarFieldWalker<'_>) -> Vec<OperatorClass> {
Expand Down
37 changes: 37 additions & 0 deletions psl/builtin-connectors/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use chrono::*;

pub(crate) fn parse_date(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
chrono::NaiveDate::parse_from_str(str, "%Y-%m-%d")
.map(|date| DateTime::<Utc>::from_utc(date.and_hms_opt(0, 0, 0).unwrap(), Utc))
.map(DateTime::<FixedOffset>::from)
}

pub(crate) fn parse_timestamptz(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
DateTime::parse_from_rfc3339(str)
}

pub(crate) fn parse_timestamp(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
NaiveDateTime::parse_from_str(str, "%Y-%m-%dT%H:%M:%S%.f")
.map(|dt| DateTime::from_utc(dt, Utc))
.or_else(|_| DateTime::parse_from_rfc3339(str).map(DateTime::<Utc>::from))
.map(DateTime::<FixedOffset>::from)
}

pub(crate) fn parse_time(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
chrono::NaiveTime::parse_from_str(str, "%H:%M:%S%.f")
.map(|time| {
let base_date = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();

DateTime::<Utc>::from_utc(base_date.and_time(time), Utc)
})
.map(DateTime::<FixedOffset>::from)
}

pub(crate) fn parse_timetz(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
// We currently don't support time with timezone.
// We strip the timezone information and parse it as a time.
// This is inline with what Quaint does already.
let time_without_tz = str.split('+').next().unwrap();

parse_time(time_without_tz)
}
9 changes: 9 additions & 0 deletions psl/psl-core/src/datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub use self::{
};

use crate::{configuration::DatasourceConnectorData, Configuration, Datasource, PreviewFeature};
use chrono::{DateTime, FixedOffset};
use diagnostics::{DatamodelError, Diagnostics, NativeTypeErrorFactory, Span};
use enumflags2::BitFlags;
use lsp_types::CompletionList;
Expand Down Expand Up @@ -359,6 +360,14 @@ pub trait Connector: Send + Sync {
) -> DatasourceConnectorData {
Default::default()
}

fn parse_json_datetime(
&self,
_str: &str,
_nt: Option<NativeTypeInstance>,
) -> chrono::ParseResult<DateTime<FixedOffset>> {
unreachable!("This method is only implemented on connectors with lateral join support.")
}
}

#[derive(Copy, Clone, Debug, PartialEq)]
Expand Down
1 change: 1 addition & 0 deletions quaint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ metrics = "0.18"
futures = "0.3"
url = "2.1"
hex = "0.4"
itertools = "0.10"

either = { version = "1.6" }
base64 = { version = "0.12.3" }
Expand Down
9 changes: 8 additions & 1 deletion quaint/src/ast/column.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Aliasable;
use super::{values::NativeColumnType, Aliasable};
use crate::{
ast::{Expression, ExpressionKind, Table},
Value,
Expand Down Expand Up @@ -32,6 +32,8 @@ pub struct Column<'a> {
pub(crate) alias: Option<Cow<'a, str>>,
pub(crate) default: Option<DefaultValue<'a>>,
pub(crate) type_family: Option<TypeFamily>,
/// The underlying native type of the column.
pub(crate) native_type: Option<NativeColumnType<'a>>,
/// Whether the column is an enum.
pub(crate) is_enum: bool,
/// Whether the column is a (scalar) list.
Expand Down Expand Up @@ -130,6 +132,11 @@ impl<'a> Column<'a> {
.map(|d| d == &DefaultValue::Generated)
.unwrap_or(false)
}

pub fn native_column_type<T: Into<NativeColumnType<'a>>>(mut self, native_type: Option<T>) -> Column<'a> {
self.native_type = native_type.map(|nt| nt.into());
self
}
}

impl<'a> From<Column<'a>> for Expression<'a> {
Expand Down
2 changes: 2 additions & 0 deletions quaint/src/ast/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ pub(crate) enum FunctionType<'a> {
JsonExtractFirstArrayElem(JsonExtractFirstArrayElem<'a>),
#[cfg(any(feature = "postgresql", feature = "mysql"))]
JsonUnquote(JsonUnquote<'a>),
#[cfg(feature = "postgresql")]
JsonArrayAgg(JsonArrayAgg<'a>),
#[cfg(feature = "postgresql")]
JsonBuildObject(JsonBuildObject<'a>),
#[cfg(any(feature = "postgresql", feature = "mysql"))]
TextSearch(TextSearch<'a>),
Expand Down
27 changes: 10 additions & 17 deletions quaint/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ pub trait Visitor<'a> {
#[cfg(any(feature = "postgresql", feature = "mysql"))]
fn visit_json_unquote(&mut self, json_unquote: JsonUnquote<'a>) -> Result;

#[cfg(feature = "postgresql")]
fn visit_json_array_agg(&mut self, array_agg: JsonArrayAgg<'a>) -> Result;

#[cfg(feature = "postgresql")]
fn visit_json_build_object(&mut self, build_obj: JsonBuildObject<'a>) -> Result;

#[cfg(any(feature = "postgresql", feature = "mysql"))]
fn visit_text_search(&mut self, text_search: TextSearch<'a>) -> Result;

Expand Down Expand Up @@ -1132,26 +1138,13 @@ pub trait Visitor<'a> {
FunctionType::Concat(concat) => {
self.visit_concat(concat)?;
}
#[cfg(feature = "postgresql")]
FunctionType::JsonArrayAgg(array_agg) => {
self.write("JSON_AGG")?;
self.surround_with("(", ")", |s| s.visit_expression(*array_agg.expr))?;
self.visit_json_array_agg(array_agg)?;
}
#[cfg(feature = "postgresql")]
FunctionType::JsonBuildObject(build_obj) => {
let len = build_obj.exprs.len();

self.write("JSON_BUILD_OBJECT")?;
self.surround_with("(", ")", |s| {
for (i, (name, expr)) in build_obj.exprs.into_iter().enumerate() {
s.visit_raw_value(Value::text(name))?;
s.write(", ")?;
s.visit_expression(expr)?;
if i < (len - 1) {
s.write(", ")?;
}
}

Ok(())
})?;
self.visit_json_build_object(build_obj)?;
}
};

Expand Down
12 changes: 11 additions & 1 deletion quaint/src/visitor/mssql.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::Visitor;
#[cfg(any(feature = "postgresql", feature = "mysql"))]
use crate::prelude::{JsonExtract, JsonType, JsonUnquote};
use crate::prelude::{JsonArrayAgg, JsonBuildObject, JsonExtract, JsonType, JsonUnquote};
use crate::{
ast::{
Column, Comparable, Expression, ExpressionKind, Insert, IntoRaw, Join, JoinData, Joinable, Merge, OnConflict,
Expand Down Expand Up @@ -656,6 +656,16 @@ impl<'a> Visitor<'a> for Mssql<'a> {
unimplemented!("JSON filtering is not yet supported on MSSQL")
}

#[cfg(feature = "postgresql")]
fn visit_json_array_agg(&mut self, _array_agg: JsonArrayAgg<'a>) -> visitor::Result {
unimplemented!("JSON_AGG is not yet supported on MSSQL")
}

#[cfg(feature = "postgresql")]
fn visit_json_build_object(&mut self, _build_obj: JsonBuildObject<'a>) -> visitor::Result {
unimplemented!("JSON_BUILD_OBJECT is not yet supported on MSSQL")
}

#[cfg(feature = "postgresql")]
fn visit_text_search(&mut self, _text_search: crate::prelude::TextSearch<'a>) -> visitor::Result {
unimplemented!("Full-text search is not yet supported on MSSQL")
Expand Down
10 changes: 10 additions & 0 deletions quaint/src/visitor/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,16 @@ impl<'a> Visitor<'a> for Mysql<'a> {
Ok(())
}

#[cfg(feature = "postgresql")]
fn visit_json_array_agg(&mut self, _array_agg: JsonArrayAgg<'a>) -> visitor::Result {
unimplemented!("JSON_ARRAYAGG is not yet supported on MySQL")
}

#[cfg(feature = "postgresql")]
fn visit_json_build_object(&mut self, _build_obj: JsonBuildObject<'a>) -> visitor::Result {
unimplemented!("JSON_OBJECT is not yet supported on MySQL")
}

fn visit_ordering(&mut self, ordering: Ordering<'a>) -> visitor::Result {
let len = ordering.0.len();

Expand Down
Loading
Loading