Skip to content

Commit

Permalink
feat(joins): support MySQL 8.0 (#4639)
Browse files Browse the repository at this point in the history
  • Loading branch information
Weakky authored Feb 6, 2024
1 parent 092a727 commit 6ba3e3d
Show file tree
Hide file tree
Showing 33 changed files with 1,320 additions and 361 deletions.
2 changes: 1 addition & 1 deletion 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 psl/psl-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ serde_json.workspace = true
enumflags2 = "0.7"
indoc.workspace = true
either = "1.8.1"
hex = "0.4"

# For the connector API.
lsp-types = "0.91.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(Connector
InsertReturning |
UpdateReturning |
RowIn |
LateralJoin |
DeleteReturning |
SupportsFiltersOnRelationsWithoutJoins
SupportsFiltersOnRelationsWithoutJoins |
LateralJoin
});

const SCALAR_TYPE_DEFAULTS: &[(ScalarType, CockroachType)] = &[
Expand Down Expand Up @@ -143,15 +143,15 @@ impl Connector for CockroachDatamodelConnector {
}
}

fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance {
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> Option<NativeTypeInstance> {
let native_type = SCALAR_TYPE_DEFAULTS
.iter()
.find(|(st, _)| st == scalar_type)
.map(|(_, native_type)| native_type)
.ok_or_else(|| format!("Could not find scalar type {scalar_type:?} in SCALAR_TYPE_DEFAULTS"))
.unwrap();

NativeTypeInstance::new::<CockroachType>(*native_type)
Some(NativeTypeInstance::new::<CockroachType>(*native_type))
}

fn native_type_is_default_for_scalar_type(
Expand Down Expand Up @@ -320,17 +320,31 @@ impl Connector for CockroachDatamodelConnector {

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

fn parse_json_bytes(&self, str: &str, nt: Option<NativeTypeInstance>) -> prisma_value::PrismaValueResult<Vec<u8>> {
let native_type: Option<&CockroachType> = nt.as_ref().map(|nt| nt.downcast_ref());

match native_type {
Some(ct) => match ct {
CockroachType::Bytes => {
super::utils::postgres::parse_bytes(str).map_err(|_| prisma_value::ConversionFailure {
from: "hex".into(),
to: "bytes".into(),
})
}
_ => unreachable!(),
},
None => self.parse_json_bytes(str, self.default_native_type_for_scalar_type(&ScalarType::Bytes)),
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions psl/psl-core/src/builtin_connectors/mongodb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ impl Connector for MongoDbDatamodelConnector {
mongodb_types::CONSTRUCTORS
}

fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance {
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> Option<NativeTypeInstance> {
let native_type = default_for(scalar_type);
NativeTypeInstance::new::<MongoDbType>(*native_type)

Some(NativeTypeInstance::new::<MongoDbType>(*native_type))
}

fn native_type_is_default_for_scalar_type(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static DEFAULT_MAPPING: Lazy<HashMap<ScalarType, MongoDbType>> = Lazy::new(|| {
(ScalarType::Float, MongoDbType::Double),
(ScalarType::Boolean, MongoDbType::Bool),
(ScalarType::String, MongoDbType::String),
(ScalarType::DateTime, MongoDbType::Timestamp),
(ScalarType::DateTime, MongoDbType::Date),
(ScalarType::Bytes, MongoDbType::BinData),
(ScalarType::Json, MongoDbType::Json),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,15 @@ impl Connector for MsSqlDatamodelConnector {
}
}

fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance {
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> Option<NativeTypeInstance> {
let nt = SCALAR_TYPE_DEFAULTS
.iter()
.find(|(st, _)| st == scalar_type)
.map(|(_, native_type)| native_type)
.ok_or_else(|| format!("Could not find scalar type {scalar_type:?} in SCALAR_TYPE_DEFAULTS"))
.unwrap();
NativeTypeInstance::new::<MsSqlType>(*nt)

Some(NativeTypeInstance::new::<MsSqlType>(*nt))
}

fn native_type_is_default_for_scalar_type(
Expand Down
33 changes: 30 additions & 3 deletions psl/psl-core/src/builtin_connectors/mysql_datamodel_connector.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod native_types;
mod validations;

use chrono::FixedOffset;
pub use native_types::MySqlType;
use prisma_value::{decode_bytes, PrismaValueResult};

use super::completions;
use crate::{
Expand Down Expand Up @@ -64,7 +66,8 @@ const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(Connector
SupportsTxIsolationRepeatableRead |
SupportsTxIsolationSerializable |
RowIn |
SupportsFiltersOnRelationsWithoutJoins
SupportsFiltersOnRelationsWithoutJoins |
CorrelatedSubqueries
});

const CONSTRAINT_SCOPES: &[ConstraintScope] = &[ConstraintScope::GlobalForeignKey, ConstraintScope::ModelKeyIndex];
Expand Down Expand Up @@ -160,15 +163,15 @@ impl Connector for MySqlDatamodelConnector {
}
}

fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance {
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> Option<NativeTypeInstance> {
let native_type = SCALAR_TYPE_DEFAULTS
.iter()
.find(|(st, _)| st == scalar_type)
.map(|(_, native_type)| native_type)
.ok_or_else(|| format!("Could not find scalar type {scalar_type:?} in SCALAR_TYPE_DEFAULTS"))
.unwrap();

NativeTypeInstance::new::<MySqlType>(*native_type)
Some(NativeTypeInstance::new::<MySqlType>(*native_type))
}

fn native_type_is_default_for_scalar_type(
Expand Down Expand Up @@ -289,4 +292,28 @@ impl Connector for MySqlDatamodelConnector {
fn flavour(&self) -> Flavour {
Flavour::Mysql
}

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

match native_type {
Some(pt) => match pt {
Date => super::utils::common::parse_date(str),
Time(_) => super::utils::common::parse_time(str),
DateTime(_) => super::utils::mysql::parse_datetime(str),
Timestamp(_) => super::utils::mysql::parse_timestamp(str),
_ => unreachable!(),
},
None => self.parse_json_datetime(str, self.default_native_type_for_scalar_type(&ScalarType::DateTime)),
}
}

// On MySQL, bytes are encoded as base64 in the database directly.
fn parse_json_bytes(&self, str: &str, _nt: Option<NativeTypeInstance>) -> PrismaValueResult<Vec<u8>> {
decode_bytes(str)
}
}
40 changes: 27 additions & 13 deletions psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(Connector
UpdateReturning |
RowIn |
DistinctOn |
LateralJoin |
DeleteReturning |
SupportsFiltersOnRelationsWithoutJoins
SupportsFiltersOnRelationsWithoutJoins |
LateralJoin
});

pub struct PostgresDatamodelConnector;
Expand Down Expand Up @@ -331,15 +331,15 @@ impl Connector for PostgresDatamodelConnector {
}
}

fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance {
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> Option<NativeTypeInstance> {
let native_type = SCALAR_TYPE_DEFAULTS
.iter()
.find(|(st, _)| st == scalar_type)
.map(|(_, native_type)| native_type)
.ok_or_else(|| format!("Could not find scalar type {scalar_type:?} in SCALAR_TYPE_DEFAULTS"))
.unwrap();

NativeTypeInstance::new::<PostgresType>(*native_type)
Some(NativeTypeInstance::new::<PostgresType>(*native_type))
}

fn native_type_is_default_for_scalar_type(
Expand Down Expand Up @@ -580,17 +580,31 @@ impl Connector for PostgresDatamodelConnector {

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

fn parse_json_bytes(&self, str: &str, nt: Option<NativeTypeInstance>) -> prisma_value::PrismaValueResult<Vec<u8>> {
let native_type: Option<&PostgresType> = nt.as_ref().map(|nt| nt.downcast_ref());

match native_type {
Some(ct) => match ct {
PostgresType::ByteA => {
super::utils::postgres::parse_bytes(str).map_err(|_| prisma_value::ConversionFailure {
from: "hex".into(),
to: "bytes".into(),
})
}
_ => unreachable!(),
},
None => self.parse_json_bytes(str, self.default_native_type_for_scalar_type(&ScalarType::Bytes)),
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ impl Connector for SqliteDatamodelConnector {
unreachable!("No native types on Sqlite");
}

fn default_native_type_for_scalar_type(&self, _scalar_type: &ScalarType) -> NativeTypeInstance {
NativeTypeInstance::new(())
fn default_native_type_for_scalar_type(&self, _scalar_type: &ScalarType) -> Option<NativeTypeInstance> {
None
}

fn native_type_is_default_for_scalar_type(
Expand Down
80 changes: 53 additions & 27 deletions psl/psl-core/src/builtin_connectors/utils.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,63 @@
use chrono::*;
pub(crate) mod common {
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_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_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();

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)
DateTime::<Utc>::from_utc(base_date.and_time(time), Utc)
})
.map(DateTime::<FixedOffset>::from)
}

pub(crate) fn parse_timestamp(str: &str, fmt: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
NaiveDateTime::parse_from_str(str, fmt)
.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();
pub(crate) mod postgres {
use chrono::*;

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

DateTime::<Utc>::from_utc(base_date.and_time(time), Utc)
})
.map(DateTime::<FixedOffset>::from)
pub(crate) fn parse_timestamp(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
super::common::parse_timestamp(str, "%Y-%m-%dT%H:%M:%S%.f")
}

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();

super::common::parse_time(time_without_tz)
}

pub(crate) fn parse_bytes(str: &str) -> Result<Vec<u8>, hex::FromHexError> {
hex::decode(&str[2..])
}
}

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();
pub(crate) mod mysql {
use chrono::*;

pub(crate) fn parse_datetime(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
super::common::parse_timestamp(str, "%Y-%m-%d %H:%M:%S%.f")
}

parse_time(time_without_tz)
pub(crate) fn parse_timestamp(str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
parse_datetime(str)
}
}
10 changes: 9 additions & 1 deletion psl/psl-core/src/datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub trait Connector: Send + Sync {

/// On each connector, each built-in Prisma scalar type (`Boolean`,
/// `String`, `Float`, etc.) has a corresponding native type.
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance;
fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> Option<NativeTypeInstance>;

/// Same mapping as `default_native_type_for_scalar_type()`, but in the opposite direction.
fn native_type_is_default_for_scalar_type(
Expand Down Expand Up @@ -321,6 +321,14 @@ pub trait Connector: Send + Sync {
) -> chrono::ParseResult<DateTime<FixedOffset>> {
unreachable!("This method is only implemented on connectors with lateral join support.")
}

fn parse_json_bytes(
&self,
_str: &str,
_nt: Option<NativeTypeInstance>,
) -> prisma_value::PrismaValueResult<Vec<u8>> {
unreachable!("This method is only implemented on connectors with lateral join support.")
}
}

#[derive(Copy, Clone, Debug, PartialEq)]
Expand Down
Loading

0 comments on commit 6ba3e3d

Please sign in to comment.