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

feat(joins): support MySQL 8.0 #4639

Merged
merged 35 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
87be7cc
feat(joins): support MySQL 8.0
Weakky Jan 11, 2024
58b6a6f
fix tests
Weakky Jan 11, 2024
e86d2e5
fix sqlite tests
Weakky Jan 11, 2024
09026ee
remove dbg
Weakky Jan 11, 2024
bbc1d47
fix test inclusion
Weakky Jan 11, 2024
3de762c
fix mongodb date/floats
Weakky Jan 15, 2024
8bf285c
fix postgres bytes decoding (encode does not support bytea[])
Weakky Jan 16, 2024
b93c7f1
fix one2m & m2m ordering (using limit hack)
Weakky Jan 18, 2024
57f3d25
remove relationJoins preview feature for vitess & mysql < 8.0
Weakky Jan 18, 2024
f8f9c3d
fix relation load strategy tests
Weakky Jan 18, 2024
73aaeef
fix null issues bcoz of mysql bug
Weakky Jan 19, 2024
7d2a8e8
test whether planetscale works now
Weakky Jan 30, 2024
8535d18
Merge branch 'main' into feat/mysql-8-joins
Weakky Jan 30, 2024
1393d06
fix planetscale & clippy
Weakky Jan 31, 2024
36ed5ce
fix pg + split mysql & pg builder
Weakky Jan 31, 2024
c55ffef
exclude pg from some tests
Weakky Feb 1, 2024
8b40b58
further refactor select builder (less code dup)
Weakky Feb 1, 2024
01d16df
do not run relationJoins for mysql 5.6, 5.7 & MariaDB
Weakky Feb 1, 2024
0348a44
herp derp
Weakky Feb 1, 2024
18ab6eb
exclude neon tests
Weakky Feb 1, 2024
bf2448d
exclude neon
Weakky Feb 1, 2024
7142b8b
remove to one hack in coercion (select builder was updated accordingly)
Weakky Feb 1, 2024
5cc3337
Merge branch 'main' into feat/mysql-8-joins
Weakky Feb 2, 2024
41d8549
unexclude a bunch of tests after null bug is fixed
Weakky Feb 2, 2024
d012a62
exclude additional test
Weakky Feb 2, 2024
99dfa65
unexclude some tests
Weakky Feb 5, 2024
0d1d55f
add light documentation
Weakky Feb 5, 2024
3beddf5
Merge branch 'main' into feat/mysql-8-joins
Weakky Feb 5, 2024
4977fed
adapt _count for mysql
Weakky Feb 5, 2024
c6d9da6
fix merge
Weakky Feb 5, 2024
2513a11
test & clippy fixes
Weakky Feb 5, 2024
2108b04
fix pg
Weakky Feb 5, 2024
085935e
light doc & refactoring
Weakky Feb 5, 2024
7d6238b
review fixes
Weakky Feb 6, 2024
a58db5f
inverted filenames
Weakky Feb 6, 2024
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: 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
Weakky marked this conversation as resolved.
Show resolved Hide resolved
});

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),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change mentioned above 👆 has rippled in unintended ways. Notably, it surfaced an incorrect default native type for the DateTime prisma type. This is why I've changed this.

(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
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 @@ -60,7 +62,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 @@ -156,15 +159,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 @@ -285,4 +288,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)
}
}
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
}
Comment on lines +69 to 71
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, now that this method is called for all connectors when constructing quaint columns, the function's signature was changed to better signal when a scalar type has no default native type.


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
Loading