Skip to content
Merged
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
6 changes: 3 additions & 3 deletions qusql-mysql-type-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qusql-mysql-type-macro"
version = "0.1.9"
version = "0.1.10"
authors = ["Jakob Truelsen <antialize@gmail.com>"]
edition = "2021"
license = "Apache-2.0"
Expand Down Expand Up @@ -28,9 +28,9 @@ list_hack = []

[dependencies]
quote = "1"
syn = { version = "2", features = ["full", "parsing"] }
syn = { version = "3", features = ["full", "parsing"] }
proc-macro2 = "1"
qusql-type = { path="../qusql-type", version="0.7.1" }
qusql-type = { path="../qusql-type", version="0.8" }
ariadne = "0.6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
2 changes: 2 additions & 0 deletions qusql-mysql-type-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ fn map_type(ta: &FullType<'_>) -> proc_macro2::TokenStream {
qusql_type::Type::Geometry => quote! {qusql_mysql_type::Any},
qusql_type::Type::Array(_) => quote! {qusql_mysql_type::Any},
qusql_type::Type::Range(_) => todo!(),
qusql_type::Type::MultiRange(_) => todo!(),
};
if !ta.not_null {
quote! {Option<#t>}
Expand Down Expand Up @@ -489,6 +490,7 @@ fn construct_row(
qusql_type::Type::Geometry => quote! {Vec<u8>},
qusql_type::Type::Array(_) => quote! {qusql_mysql_type::Any},
qusql_type::Type::Range(_) => todo!(),
qusql_type::Type::MultiRange(_) => todo!(),
};
let name = match &c.name {
Some(v) => v,
Expand Down
6 changes: 3 additions & 3 deletions qusql-mysql-type/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qusql-mysql-type"
version = "0.1.9"
version = "0.1.10"
edition = "2024"
license = "Apache-2.0"
keywords = [ "mysql" ]
Expand All @@ -26,6 +26,6 @@ chrono = ["dep:chrono", "qusql-mysql/chrono"]
list_hack = ["qusql-mysql/list_hack", "qusql-mysql-type-macro/list_hack"]

[dependencies]
qusql-mysql-type-macro={path="../qusql-mysql-type-macro", version="0.1.9"}
qusql-mysql={path="../qusql-mysql", version="0.1.0"}
qusql-mysql-type-macro={path="../qusql-mysql-type-macro", version="0.1.10"}
qusql-mysql={path="../qusql-mysql", version="0.1.1"}
chrono = {version = "0.4", optional=true}
2 changes: 1 addition & 1 deletion qusql-parse/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qusql-parse"
version = "0.10.0"
version = "0.11.0"
edition = "2024"
authors = ["Jakob Truelsen <antialize@gmail.com>"]
keywords = [ "mysql", "postgresql", "sql", "lexer", "parser" ]
Expand Down
79 changes: 79 additions & 0 deletions qusql-parse/src/function_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,26 @@ pub enum Function<'a> {
ArrayUpper,
Cardinality,
TrimArray,
// PostgreSQL range/multirange functions
IsEmpty,
LowerInc,
LowerInf,
UpperInc,
UpperInf,
RangeMerge,
Multirange,
Int4Range,
Int8Range,
NumRange,
TsRange,
TstzRange,
DateRange,
Int4Multirange,
Int8Multirange,
NumMultirange,
TsMultirange,
TstzMultirange,
DateMultirange,
// PostgreSQL text search functions
ArrayToTsvector,
GetCurrentTsConfig,
Expand Down Expand Up @@ -2605,6 +2625,65 @@ pub(crate) fn parse_function<'a>(
Function::TrimArray
}

// PostgreSQL range/multirange functions
Token::Ident(_, Keyword::ISEMPTY) if parser.options.dialect.is_postgresql() => {
Function::IsEmpty
}
Token::Ident(_, Keyword::LOWER_INC) if parser.options.dialect.is_postgresql() => {
Function::LowerInc
}
Token::Ident(_, Keyword::LOWER_INF) if parser.options.dialect.is_postgresql() => {
Function::LowerInf
}
Token::Ident(_, Keyword::UPPER_INC) if parser.options.dialect.is_postgresql() => {
Function::UpperInc
}
Token::Ident(_, Keyword::UPPER_INF) if parser.options.dialect.is_postgresql() => {
Function::UpperInf
}
Token::Ident(_, Keyword::RANGE_MERGE) if parser.options.dialect.is_postgresql() => {
Function::RangeMerge
}
Token::Ident(_, Keyword::MULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::Multirange
}
Token::Ident(_, Keyword::INT4RANGE) if parser.options.dialect.is_postgresql() => {
Function::Int4Range
}
Token::Ident(_, Keyword::INT8RANGE) if parser.options.dialect.is_postgresql() => {
Function::Int8Range
}
Token::Ident(_, Keyword::NUMRANGE) if parser.options.dialect.is_postgresql() => {
Function::NumRange
}
Token::Ident(_, Keyword::TSRANGE) if parser.options.dialect.is_postgresql() => {
Function::TsRange
}
Token::Ident(_, Keyword::TSTZRANGE) if parser.options.dialect.is_postgresql() => {
Function::TstzRange
}
Token::Ident(_, Keyword::DATERANGE) if parser.options.dialect.is_postgresql() => {
Function::DateRange
}
Token::Ident(_, Keyword::INT4MULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::Int4Multirange
}
Token::Ident(_, Keyword::INT8MULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::Int8Multirange
}
Token::Ident(_, Keyword::NUMMULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::NumMultirange
}
Token::Ident(_, Keyword::TSMULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::TsMultirange
}
Token::Ident(_, Keyword::TSTZMULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::TstzMultirange
}
Token::Ident(_, Keyword::DATEMULTIRANGE) if parser.options.dialect.is_postgresql() => {
Function::DateMultirange
}

// PostgreSQL text search functions
Token::Ident(_, Keyword::ARRAY_TO_TSVECTOR) if parser.options.dialect.is_postgresql() => {
Function::ArrayToTsvector
Expand Down
7 changes: 7 additions & 0 deletions qusql-parse/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@ pub(crate) enum Keyword {
IS_IPV6,
IS_USED_LOCK,
IS_UUID,
ISEMPTY,
ISOLATION,
ISCLOSED,
ISOPEN,
Expand Down Expand Up @@ -879,6 +880,8 @@ pub(crate) enum Keyword {
LOGIN,
LOGS,
LOWER,
LOWER_INC,
LOWER_INF,
LPAD,
LSEG,
LTRIM,
Expand Down Expand Up @@ -945,6 +948,7 @@ pub(crate) enum Keyword {
MONITOR,
MONTH,
MONTHNAME,
MULTIRANGE,
MUTEX,
MXID_AGE,
MYSQL_ERRNO,
Expand Down Expand Up @@ -1159,6 +1163,7 @@ pub(crate) enum Keyword {
RAND,
RANDOM_BYTES,
RANDOM_NORMAL,
RANGE_MERGE,
RANK,
RAW,
READ_ONLY,
Expand Down Expand Up @@ -1655,6 +1660,8 @@ pub(crate) enum Keyword {
UPDATEXML,
UPGRADE,
UPPER,
UPPER_INC,
UPPER_INF,
USE_FRM,
USER_RESOURCES,
USER,
Expand Down
4 changes: 2 additions & 2 deletions qusql-py-mysql-type-plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ name = "qusql_mysql_type_plugin"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.28", features = ["extension-module"] }
qusql-type = {version="0.7.0", path="../qusql-type" }
pyo3 = { version = "0.29", features = ["extension-module"] }
qusql-type = {version="0.8.0", path="../qusql-type" }
ariadne = "0.6"
yoke = { version ="0.8", features = ["derive", "alloc"] }
1 change: 1 addition & 0 deletions qusql-py-mysql-type-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ fn map_type(t: &qusql_type::FullType<'_>) -> Type {
qusql_type::Type::Geometry => Type::Any,
qusql_type::Type::Array(_) => Type::Any,
qusql_type::Type::Range(_) => Type::Any,
qusql_type::Type::MultiRange(_) => Type::Any,
qusql_type::Type::Set(_) => Type::String,
qusql_type::Type::U16 => Type::Integer,
qusql_type::Type::U24 => Type::Integer,
Expand Down
6 changes: 3 additions & 3 deletions qusql-sqlx-type-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qusql-sqlx-type-macro"
version = "0.4.2"
version = "0.4.3"
authors = ["Jakob Truelsen <antialize@gmail.com>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -25,9 +25,9 @@ proc-macro = true

[dependencies]
quote = "1"
syn = { version = "2", features = ["full", "parsing"] }
syn = { version = "3", features = ["full", "parsing"] }
proc-macro2 = "1"
qusql-type = { path = "../qusql-type", version="0.7.2"}
qusql-type = { path = "../qusql-type", version="0.8.0"}
ariadne = "0.6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
57 changes: 51 additions & 6 deletions qusql-sqlx-type-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,41 @@ fn get_schemas() -> Arc<SchemaCacheEntry> {
entry
}

/// Rust type used to bind/read a PostgreSQL range column, keyed by the (boxed) element
/// `qusql_type::Type` of a `qusql_type::Type::Range`/`Type::MultiRange`.
///
/// `sqlx::postgres::types::PgRange<T>` (re-exported as `qusql_sqlx_type::PgRange`) only has
/// built-in `sqlx` support for a handful of element types without pulling in extra optional
/// dependencies (in particular `NUMRANGE`/numeric would need the `bigdecimal` or
/// `rust_decimal` crate, which this crate does not integrate). Dialects other than
/// PostgreSQL never produce range types, and unsupported element types fall back to
/// `fallback`.
fn quote_range_elem_type(
elem: &qusql_type::Type<'_>,
is_postgres: bool,
fallback: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
if !is_postgres {
return fallback;
}
match elem {
// `int4range`/`int4multirange`
qusql_type::Type::I32 => quote! { qusql_sqlx_type::PgRange<i32> },
// `int8range`/`int8multirange`
qusql_type::Type::I64 => quote! { qusql_sqlx_type::PgRange<i64> },
qusql_type::Type::Base(qusql_type::BaseType::Date) => {
quote! { qusql_sqlx_type::PgRange<chrono::NaiveDate> }
}
qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
quote! { qusql_sqlx_type::PgRange<chrono::NaiveDateTime> }
}
qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
quote! { qusql_sqlx_type::PgRange<chrono::DateTime<chrono::Utc>> }
}
_ => fallback,
}
}

/// Produce quoted arguments for a query
fn quote_args(
errors: &mut Vec<proc_macro2::TokenStream>,
Expand All @@ -285,6 +320,7 @@ fn quote_args(
SQLDialect::Sqlite => quote!(sqlx::sqlite::Sqlite),
SQLDialect::PostgreSQL | SQLDialect::PostGIS => quote!(sqlx::postgres::Postgres),
};
let is_postgres = dialect.is_postgresql();

let mut at = Vec::new();
let inv = qusql_type::FullType::invalid();
Expand Down Expand Up @@ -331,7 +367,7 @@ fn quote_args(
let mut list_lengths = Vec::new();

for ((qa, ta), name) in args.iter().zip(at).zip(&arg_names) {
let mut t = match ta.t {
let mut t = match &ta.t {
qusql_type::Type::U8 => quote! {u8},
qusql_type::Type::I8 => quote! {i8},
qusql_type::Type::U16 => quote! {u16},
Expand Down Expand Up @@ -369,7 +405,10 @@ fn quote_args(
qusql_type::Type::F64 => quote! {f64},
qusql_type::Type::JSON => quote! {qusql_sqlx_type::Any},
qusql_type::Type::Geometry => quote! {qusql_sqlx_type::Any},
qusql_type::Type::Range(_) => quote! {qusql_sqlx_type::Any},
qusql_type::Type::Range(elem) => {
quote_range_elem_type(elem, is_postgres, quote! {qusql_sqlx_type::Any})
}
qusql_type::Type::MultiRange(_) => quote! {qusql_sqlx_type::Any},
qusql_type::Type::Array(_) => quote! {qusql_sqlx_type::Any},
};
if !ta.not_null {
Expand Down Expand Up @@ -468,7 +507,7 @@ fn construct_row(
let mut row_members = Vec::new();
let mut row_construct = Vec::new();
for (i, c) in columns.iter().enumerate() {
let mut t = match c.type_.t {
let mut t = match &c.type_.t {
qusql_type::Type::U8 => quote! {u8},
qusql_type::Type::I8 => quote! {i8},
qusql_type::Type::U16 => quote! {u16},
Expand Down Expand Up @@ -521,7 +560,10 @@ fn construct_row(
}
}
qusql_type::Type::Geometry => quote! {Vec<u8>},
qusql_type::Type::Range(_) => quote! {Vec<u8>},
qusql_type::Type::MultiRange(_) => quote! {Vec<u8>},
qusql_type::Type::Range(elem) => {
quote_range_elem_type(elem, is_postgres, quote! {Vec<u8>})
}
qusql_type::Type::Array(_) => quote! {qusql_sqlx_type::Any},
};
let name = match &c.name {
Expand Down Expand Up @@ -893,7 +935,7 @@ fn construct_row2(
) -> Vec<proc_macro2::TokenStream> {
let mut row_construct = Vec::new();
for (i, c) in columns.iter().enumerate() {
let mut t = match c.type_.t {
let mut t = match &c.type_.t {
qusql_type::Type::U8 => quote! {u8},
qusql_type::Type::I8 => quote! {i8},
qusql_type::Type::U16 => quote! {u16},
Expand Down Expand Up @@ -946,7 +988,10 @@ fn construct_row2(
}
}
qusql_type::Type::Geometry => quote! {Vec<u8>},
qusql_type::Type::Range(_) => quote! {Vec<u8>},
qusql_type::Type::MultiRange(_) => quote! {Vec<u8>},
qusql_type::Type::Range(elem) => {
quote_range_elem_type(elem, is_postgres, quote! {Vec<u8>})
}
qusql_type::Type::Array(_) => quote! {qusql_sqlx_type::Any},
};
let name = match &c.name {
Expand Down
5 changes: 4 additions & 1 deletion qusql-sqlx-type-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ edition = "2024"
publish = false

[dev-dependencies]
qusql-sqlx-type = { path = "../qusql-sqlx-type", features = ["json"] }
qusql-sqlx-type = { path = "../qusql-sqlx-type", features = ["json", "postgres", "uuid"] }
sqlx = { version = "0.9", default-features = false, features = [
"postgres",
"runtime-tokio",
"chrono",
"json",
"migrate",
"macros",
"uuid",
] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
chrono = "0.4"
uuid = "1"
10 changes: 9 additions & 1 deletion qusql-sqlx-type-test/sqlx-type-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ CREATE TABLE IF NOT EXISTS type_test_items (
score integer NOT NULL DEFAULT 0,
active boolean NOT NULL DEFAULT true,
ratio float8 NOT NULL DEFAULT 0.0,
props jsonb NOT NULL DEFAULT '{}'
props jsonb NOT NULL DEFAULT '{}',
validity daterange
);

CREATE SEQUENCE IF NOT EXISTS type_test_seq;

CREATE TABLE IF NOT EXISTS partial_files (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
uploaded_bytes int8multirange NOT NULL DEFAULT '{}',
last_modified timestamptz NOT NULL DEFAULT now()
);
Comment thread
antialize marked this conversation as resolved.

Loading