Skip to content

Commit

Permalink
Merge branch 'main' into feat/planetscale-column-type
Browse files Browse the repository at this point in the history
  • Loading branch information
jkomyno committed Jul 24, 2023
2 parents 039fe04 + 1548dc1 commit 9a12926
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 20 deletions.
12 changes: 11 additions & 1 deletion psl/builtin-connectors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod mongodb;
mod mssql_datamodel_connector;
mod mysql_datamodel_connector;
mod native_type_definition;
mod planetscale;
mod postgres_datamodel_connector;
mod sqlite_datamodel_connector;

Expand All @@ -25,5 +26,14 @@ pub const MYSQL: &'static dyn Connector = &mysql_datamodel_connector::MySqlDatam
pub const SQLITE: &'static dyn Connector = &sqlite_datamodel_connector::SqliteDatamodelConnector;
pub const MSSQL: &'static dyn Connector = &mssql_datamodel_connector::MsSqlDatamodelConnector;
pub const MONGODB: &'static dyn Connector = &mongodb::MongoDbDatamodelConnector;
pub static PLANETSCALE_SERVERLESS: &'static dyn Connector = &planetscale::PLANETSCALE_SERVERLESS;

pub const BUILTIN_CONNECTORS: ConnectorRegistry = &[POSTGRES, MYSQL, SQLITE, MSSQL, COCKROACH, MONGODB];
pub static BUILTIN_CONNECTORS: ConnectorRegistry = &[
POSTGRES,
MYSQL,
SQLITE,
MSSQL,
COCKROACH,
MONGODB,
PLANETSCALE_SERVERLESS,
];
2 changes: 1 addition & 1 deletion psl/builtin-connectors/src/mysql_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl Connector for MySqlDatamodelConnector {
}

fn is_provider(&self, name: &str) -> bool {
name == "mysql" || name == "@prisma/mysql"
name == "mysql"
}

fn capabilities(&self) -> ConnectorCapabilities {
Expand Down
15 changes: 15 additions & 0 deletions psl/builtin-connectors/src/planetscale.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::mysql_datamodel_connector;
use psl_core::{
datamodel_connector::RelationMode,
js_connector::{Flavor, JsConnector},
};

pub(crate) static PLANETSCALE_SERVERLESS: JsConnector = JsConnector {
flavor: Flavor::MySQL,
canonical_connector: &mysql_datamodel_connector::MySqlDatamodelConnector,

provider_name: "@prisma/planetscale",
name: "planetscale serverless",
enforced_relation_mode: Some(RelationMode::Prisma),
allowed_protocols: Some(&["mysql", "https", "mysqls"]),
};
9 changes: 8 additions & 1 deletion psl/psl-core/src/datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub use self::{
relation_mode::RelationMode,
};

use crate::{configuration::DatasourceConnectorData, Configuration, Datasource, PreviewFeature};
use crate::{
configuration::DatasourceConnectorData, js_connector::JsConnector, Configuration, Datasource, PreviewFeature,
};
use diagnostics::{DatamodelError, Diagnostics, NativeTypeErrorFactory, Span};
use enumflags2::BitFlags;
use lsp_types::CompletionList;
Expand All @@ -41,6 +43,11 @@ pub const EXTENSIONS_KEY: &str = "extensions";

/// The datamodel connector API.
pub trait Connector: Send + Sync {
// Provides safe downcasting to a JsConnector, in case it is one.
fn as_js_connector(&self) -> Option<JsConnector> {
None
}

/// The name of the provider, for string comparisons determining which connector we are on.
fn provider_name(&self) -> &'static str;

Expand Down
126 changes: 126 additions & 0 deletions psl/psl-core/src/js_connector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use crate::datamodel_connector::*;
use enumflags2::BitFlags;

/// JsConnector represents a type of connector that is implemented partially
/// in javascript and used from rust through the js-connectors crate
///
/// Rather than a unit struct per individual connector, like we have for the rest
/// of the builtin connectors, we have a single struct which state represents the
/// features that vary in this connector with respect to a cannonical connector
/// for the flavor of SQL the particular JsConnector speaks.
///
/// For example, the _planetscale serverless_ connector is compatible with MySQL,
/// so it reuses the builtin MySQL connector (the cannonical for the MySQL flavor)
/// for most of its features.
#[derive(Copy, Clone)]
pub struct JsConnector {
pub flavor: Flavor,
pub canonical_connector: &'static dyn Connector,

pub provider_name: &'static str,
pub name: &'static str,
pub enforced_relation_mode: Option<RelationMode>,
pub allowed_protocols: Option<&'static [&'static str]>,
}

#[derive(Copy, Clone)]
pub enum Flavor {
MySQL,
}

impl Connector for JsConnector {
fn as_js_connector(&self) -> Option<JsConnector> {
Some(*self)
}

fn provider_name(&self) -> &'static str {
self.provider_name
}

fn name(&self) -> &str {
self.name
}

fn capabilities(&self) -> ConnectorCapabilities {
self.canonical_connector.capabilities()
}

fn max_identifier_length(&self) -> usize {
self.canonical_connector.max_identifier_length()
}

fn referential_actions(&self) -> enumflags2::BitFlags<parser_database::ReferentialAction> {
self.canonical_connector.referential_actions()
}

fn available_native_type_constructors(&self) -> &'static [NativeTypeConstructor] {
self.canonical_connector.available_native_type_constructors()
}

fn scalar_type_for_native_type(&self, native_type: &NativeTypeInstance) -> parser_database::ScalarType {
self.canonical_connector.scalar_type_for_native_type(native_type)
}

fn default_native_type_for_scalar_type(&self, scalar_type: &parser_database::ScalarType) -> NativeTypeInstance {
self.canonical_connector
.default_native_type_for_scalar_type(scalar_type)
}

fn native_type_is_default_for_scalar_type(
&self,
native_type: &NativeTypeInstance,
scalar_type: &parser_database::ScalarType,
) -> bool {
self.canonical_connector
.native_type_is_default_for_scalar_type(native_type, scalar_type)
}

fn native_type_to_parts(&self, native_type: &NativeTypeInstance) -> (&'static str, Vec<String>) {
self.canonical_connector.native_type_to_parts(native_type)
}

fn parse_native_type(
&self,
name: &str,
args: &[String],
span: diagnostics::Span,
diagnostics: &mut diagnostics::Diagnostics,
) -> Option<NativeTypeInstance> {
self.canonical_connector
.parse_native_type(name, args, span, diagnostics)
}

fn validate_url(&self, url: &str) -> Result<(), String> {
if let Some(allowed_protocols) = self.allowed_protocols {
let scheme = url.split(':').next().unwrap_or("");
if allowed_protocols.contains(&scheme) {
Ok(())
} else {
Err(format!(
"The URL scheme `{}` is not valid for the {} connector. The following schemes are allowed: {}",
scheme,
self.name,
allowed_protocols.join(", ")
))
}
} else {
self.canonical_connector.validate_url(url)
}
}

fn default_relation_mode(&self) -> RelationMode {
if let Some(relation_mode) = self.enforced_relation_mode {
relation_mode
} else {
self.canonical_connector.default_relation_mode()
}
}

fn allowed_relation_mode_settings(&self) -> BitFlags<RelationMode> {
if let Some(relation_mode) = self.enforced_relation_mode {
BitFlags::from(relation_mode)
} else {
self.canonical_connector.allowed_relation_mode_settings()
}
}
}
1 change: 1 addition & 0 deletions psl/psl-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![allow(clippy::derive_partial_eq_without_eq)]

pub mod datamodel_connector;
pub mod js_connector;

/// `mcf`: Turns a collection of `configuration::Datasource` and `configuration::Generator` into a
/// JSON representation. This is the `get_config()` representation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "@prisma/planetscale"
url = "mysql://"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "@prisma/planetscale"
url = "mysql://"
relationMode = "foreignKeys"
}
// error: Error validating datasource `relationMode`: Invalid relation mode setting: "foreignKeys". Supported values: "prisma"
// --> schema.prisma:8
//  | 
//  7 |  url = "mysql://"
//  8 |  relationMode = "foreignKeys"
//  | 
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "@prisma/planetscale"
url = "https://"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "@prisma/planetscale"
url = "mysqls://"
}

This file was deleted.

21 changes: 19 additions & 2 deletions quaint/src/connector/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::{
time::Duration,
};
use tokio::sync::Mutex;
use url::Url;
use url::{Host, Url};

/// The underlying MySQL driver. Only available with the `expose-drivers`
/// Cargo feature.
Expand Down Expand Up @@ -98,7 +98,18 @@ impl MysqlUrl {

/// The database host. If `socket` and `host` are not set, defaults to `localhost`.
pub fn host(&self) -> &str {
self.url.host_str().unwrap_or("localhost")
match (self.url.host(), self.url.host_str()) {
(Some(Host::Ipv6(_)), Some(host)) => {
// The `url` crate may return an IPv6 address in brackets, which must be stripped.
if host.starts_with('[') && host.ends_with(']') {
&host[1..host.len() - 1]
} else {
host
}
}
(_, Some(host)) => host,
_ => "localhost",
}
}

/// If set, connected to the database through a Unix socket.
Expand Down Expand Up @@ -604,6 +615,12 @@ mod tests {
assert!(!url.query_params.ssl_opts.accept_invalid_certs());
}

#[test]
fn should_parse_ipv6_host() {
let url = MysqlUrl::new(Url::parse("mysql://[2001:db8:1234::ffff]:5432/testdb").unwrap()).unwrap();
assert_eq!("2001:db8:1234::ffff", url.host());
}

#[test]
fn should_allow_changing_of_cache_size() {
let url = MysqlUrl::new(Url::parse("mysql:///root:root@localhost:3307/foo?statement_cache_size=420").unwrap())
Expand Down
26 changes: 20 additions & 6 deletions quaint/src/connector/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use tokio_postgres::{
config::{ChannelBinding, SslMode},
Client, Config, Statement,
};
use url::Url;
use url::{Host, Url};

pub(crate) const DEFAULT_SCHEMA: &str = "public";

Expand Down Expand Up @@ -223,11 +223,19 @@ impl PostgresUrl {
///
/// If none of them are set, defaults to `localhost`.
pub fn host(&self) -> &str {
match (self.query_params.host.as_ref(), self.url.host_str()) {
(Some(host), _) => host.as_str(),
(None, Some("")) => "localhost",
(None, None) => "localhost",
(None, Some(host)) => host,
match (self.query_params.host.as_ref(), self.url.host_str(), self.url.host()) {
(Some(host), _, _) => host.as_str(),
(None, Some(""), _) => "localhost",
(None, None, _) => "localhost",
(None, Some(host), Some(Host::Ipv6(_))) => {
// The `url` crate may return an IPv6 address in brackets, which must be stripped.
if host.starts_with('[') && host.ends_with(']') {
&host[1..host.len() - 1]
} else {
host
}
}
(None, Some(host), _) => host,
}
}

Expand Down Expand Up @@ -1142,6 +1150,12 @@ mod tests {
assert_eq!("localhost", url.host());
}

#[test]
fn should_parse_ipv6_host() {
let url = PostgresUrl::new(Url::parse("postgresql://[2001:db8:1234::ffff]:5432/dbname").unwrap()).unwrap();
assert_eq!("2001:db8:1234::ffff", url.host());
}

#[test]
fn should_handle_options_field() {
let url = PostgresUrl::new(Url::parse("postgresql:///localhost:5432?options=--cluster%3Dmy_cluster").unwrap())
Expand Down

0 comments on commit 9a12926

Please sign in to comment.