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

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
@@ -14,6 +14,7 @@ in
nodejs.pkgs.typescript-language-server
nodejs.pkgs.pnpm

cargo-insta
jq
graphviz
wasm-bindgen-cli
2 changes: 2 additions & 0 deletions psl/builtin-connectors/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
@@ -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::{
@@ -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.
1 change: 1 addition & 0 deletions psl/builtin-connectors/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};

24 changes: 24 additions & 0 deletions psl/builtin-connectors/src/postgres_datamodel_connector.rs
Original file line number Diff line number Diff line change
@@ -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::{
@@ -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> {
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
@@ -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;
@@ -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)]
1 change: 1 addition & 0 deletions quaint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
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,
@@ -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.
@@ -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> {
2 changes: 2 additions & 0 deletions quaint/src/ast/function.rs
Original file line number Diff line number Diff line change
@@ -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>),
27 changes: 10 additions & 17 deletions quaint/src/visitor.rs
Original file line number Diff line number Diff line change
@@ -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;

@@ -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)?;
}
};

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,
@@ -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")
10 changes: 10 additions & 0 deletions quaint/src/visitor/mysql.rs
Original file line number Diff line number Diff line change
@@ -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();

Loading
Loading